From 8d2d6526309a344ff560ddc6e6f0afaac1e6ee20 Mon Sep 17 00:00:00 2001 From: deepvasoya Date: Tue, 20 May 2025 10:43:33 +0530 Subject: [PATCH] feat: clinic approval flow --- .gitignore | 2 + package-lock.json | 9 + package.json | 1 + src/assets/images/icon/jpeg.png | Bin 0 -> 18679 bytes src/assets/images/icon/jpg.png | Bin 0 -> 18683 bytes src/assets/images/icon/phone.svg | 1 + src/assets/images/icon/png.png | Bin 0 -> 13174 bytes src/assets/images/icon/svg.png | Bin 0 -> 12103 bytes src/common/envVariables.js | 2 +- src/components/CustomFileUpload.jsx | 57 +- src/components/ImagePreviewComponent.jsx | 114 ++++ .../styles/ImagePreviewComponentStyles.js | 55 ++ src/config/api.js | 22 +- src/config/firebase.js | 36 +- src/constants/index.js | 16 + src/context/FirebaseProvider.jsx | 13 +- src/layouts/mainLayout/components/Header.jsx | 4 +- .../mainLayout/components/ProfileSection.jsx | 11 +- src/layouts/mainLayout/components/Sidebar.jsx | 8 +- .../mainLayout/components/sideBarConfig.js | 15 +- src/redux/mockUserData.js | 4 +- src/redux/setMockUser.js | 2 +- src/redux/userRoleSlice.js | 6 - src/routes/index.js | 2 + src/routes/withPermission.jsx | 6 +- src/services/auth.services.js | 11 + src/services/clinics.service.js | 117 ++-- src/services/dashboard.services.js | 33 + src/services/file.upload.services.js | 24 + src/services/jwt.services.js | 5 + src/utils/share.js | 6 +- .../ClinicDetails/component/FileEvaluate.jsx | 176 +++-- .../component/GeneralInformation.jsx | 42 +- src/views/ClinicDetails/index.jsx | 248 ++++--- src/views/ClinicsList/index.jsx | 34 +- .../Dashboard/Tiles/SuperAdminTotals.jsx | 4 +- src/views/Dashboard/Tiles/Totals.jsx | 8 +- .../Dashboard/components/PaymentConfig.jsx | 205 ++++++ src/views/Dashboard/components/SuperAdmin.jsx | 27 +- src/views/Dashboard/index.jsx | 57 +- src/views/Login/LoginForm.jsx | 5 +- src/views/Login/loginAction.js | 186 +++--- src/views/Login/loginReducer.js | 6 +- src/views/MasterData/index.jsx | 611 +++++++++--------- src/views/MockPayment/index.jsx | 54 +- src/views/Notifications/notificationUtils.js | 14 +- src/views/PaymentManagement/index.jsx | 330 ++++++++++ src/views/Signup/YourDetailsForm.jsx | 381 ++++++----- src/views/Signup/signupReducer.js | 27 +- src/views/User/index.jsx | 2 +- 50 files changed, 1929 insertions(+), 1070 deletions(-) create mode 100644 src/assets/images/icon/jpeg.png create mode 100644 src/assets/images/icon/jpg.png create mode 100644 src/assets/images/icon/phone.svg create mode 100644 src/assets/images/icon/png.png create mode 100644 src/assets/images/icon/svg.png create mode 100644 src/components/ImagePreviewComponent.jsx create mode 100644 src/components/styles/ImagePreviewComponentStyles.js create mode 100644 src/services/auth.services.js create mode 100644 src/services/dashboard.services.js create mode 100644 src/services/jwt.services.js create mode 100644 src/views/Dashboard/components/PaymentConfig.jsx create mode 100644 src/views/PaymentManagement/index.jsx diff --git a/.gitignore b/.gitignore index a547bf3..3b0b403 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ dist-ssr *.njsproj *.sln *.sw? + +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2aabaa7..cee5ead 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "firebase": "^11.6.0", "formik": "^2.4.6", "i": "^0.3.7", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "material-react-table": "^3.2.1", "npm": "^11.3.0", @@ -4184,6 +4185,14 @@ "jss": "10.10.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", diff --git a/package.json b/package.json index 8cbaeb2..797cfd4 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "firebase": "^11.6.0", "formik": "^2.4.6", "i": "^0.3.7", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "material-react-table": "^3.2.1", "npm": "^11.3.0", diff --git a/src/assets/images/icon/jpeg.png b/src/assets/images/icon/jpeg.png new file mode 100644 index 0000000000000000000000000000000000000000..3077a236f0141a260387a4289db715470350596e GIT binary patch literal 18679 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 IM6N<$f)*3yjsO4v literal 0 HcmV?d00001 diff --git a/src/assets/images/icon/jpg.png b/src/assets/images/icon/jpg.png new file mode 100644 index 0000000000000000000000000000000000000000..04f0db89dfdf6b4206f112c9d726e751d342d383 GIT binary patch literal 18683 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 \ No newline at end of file diff --git a/src/assets/images/icon/png.png b/src/assets/images/icon/png.png new file mode 100644 index 0000000000000000000000000000000000000000..6425812500641c375fb8903d31702add6d2f72fa GIT binary patch literal 13174 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 I&-}&z0&qsQLjV8( literal 0 HcmV?d00001 diff --git a/src/assets/images/icon/svg.png b/src/assets/images/icon/svg.png new file mode 100644 index 0000000000000000000000000000000000000000..562fc38208f4d65398df73a41beee72935bfe9bd GIT binary patch literal 12103 zcmd6NcTkgE_ux$kBuGc3DP0r=X@Yw zUPPMGrFV$*knDYYzwi6)?(EFY?EL=NcgB~S``mK+Irp3sVth`IX%Fun007gO)7lpR zK*J##;OO9=KlguZ!$0({r>)!pVE%&qL&b?R?*~8-oYB@Wz5ili@Q$<1TFTPOQuyKH z_pXfPJz@QIT>iaB+`#_XwVphU%2ZWCn)I2>OwB%Qsd%sG+3p3pTiIE;9(T|xM{ah< zr4mIW6tr*Wdz6OgpWc6XCI)NaDmrFZl9!+7@+Llp`&LK)vEOD{E(jPPo=TTV~c(UiYn@wQS{P|;jop}CSu z8s4Ko9Ta&4HhEEkBb=B1)a~87V6%)`l6US?#}SrfUhg+XZX!R~5!kf7e`QQl(Uiof zMJ8-qKXho4I#6&q&~SGQPref|0h)yYrp5JW@f^#B+Hz&XpFi}H+k}h7b3KF08+~bK z@me^79UDMjQH=ZPNqKmTc5xkr54rvGk3s^~lS-Xa4c=h=2jH`I2c~My z;t0d8_0m6Fjqc1f#7hZW%@B~*T9{JZSYV;-4C#6Hq7-p}j`!UAdqIZ}Nd`BZ7%T>2 zw)0QV?%+_%3}}MJW1k1sDWm>ydb2F362!CT_*Vr*^(UVQQ?t=SYHZS``Cc@gUQ+B<8SPK$H8CA(cDbJF59FWj7lj1)LxQ z(1*8C*Npu)C)oKyUCA4agg{^mVyx$q9$Cp z2LoDlOMJHkx$q#YQw4WP1l*K^2(j~SgNNs5Bnek$7b~7kYAiFN)Sn?pCvAgI7h3;o z9s}p+H|s4tdI6CU9>|3QywXc7sR#ZUhuR7>ISdJ?TTLco+0Y~GXu=hXbV;?`N;+`; zjW_FnHZZ;k`Ci|l#%klTgtlzTn!H~A9UoqU%HTz*vzhPGe4474B)sp?V094$V(c%U zZx$}{0t~h_(D)ddfbQj5C|G78WZ&d}TzwrHj z=^{JGczxwBeKgB)fZt(4&7Q@q5`@0?W^K{{%N=Vjc#vucMdT5PeFR2cJJ&3G4$6Z9 zgR+`SSG=6x1PYpKgJp!?;^`Drhmeh(5mqB77>w-Wq@p^4Cp9eTQA80&D$ROlITRH+ zmd|eR8v9Lwf@h1izyMwoDg^Ees>v6h_hJGgsot#dnqXP|>VpSQAWHT^N!ZIWXae>+ z$@?jVc0?KO*TjGXrj0)(x}U3Z06An*!`1b@8xM&}s3w;{AxOe%KsYOw_o0EkxY{CG z?D3?AASjbKkX5b+Z!q4Igzb+2BSkai{q56vD4?%H67*pMHeXTdzz_-|GX9eI3m9sX z1eMtUAd05Vqb>Czx;^-}36mO}LZB@87=bWo_>;DP<0S595VWAr^{^T~tZ<-^>K(<0 z1CnE`ffn|cNH;iB^ zILd!Tfv|Ivf1DYDet){|*Kn<~=wTtzcZB?+4w4<= zQ~P8}K+CaQL?Dy1KdR1@gx!bGnd$i+V+fT20+r-9qlTTSwwb)od*oQQ6(LSH+fCM4OW5;Kfw~F z+M5;9$M7EcQF!$-x(m%u??w`Yz;L^aegqmsKaJb`+7MELYXDK1x>n1EvEleJslhf# z3&4N^BvnIraEpJ9iObAvj9F`Ig@No(UsCG!wzW0Oyg-j_ zmhJs65u~j-tKbrw%ucV1)t4StnI=-A7B6OLK+MCGF56FfTpVhgctBSOdB zNJ6R4Lu}SBDwe4_MoDp8^(@o9zQ@~a!qH;?9=x@7I0HCnJlWJe$n=lqftcH1ttjk*c|Pv5+K}M%^6_Q1Ir0E{*dwM@88Wj)346PA3s@ z1afg7L|v1JPOIU(Zq5WcniAQi*T$v}>B1w-@0KvIqg%e*fqrG5E)5(o0Au-NJsQK^ z#MT^MFk*1Rh3hI4TzO^qo`wnj-0^HWu=(=XTNI*g1;Mx9q7IEJpuzHsTn5Z;_%Iz% z4|#l3F6u2RjiQ3wCT-rKOJhj`IS>-yaX%_kEiloH2846P)&+7y2~0(oICK5kk8nu2 zgdyvjI1^ZA+6%8|I)?1VNp$_Qn-FGbC3sUa!axSWY@cDDWCIj2z>jOXr%^lFjRD-B z9(96e+8$gWW7%Kwu*IsoY+~!IByGm&BA4Qv5bcBPB|BqN;dG}Zr;=ZLnb6Rqz{B-( z_jJ#lz3!2KGF@mD7YGe*n4-QppX4+rXYtk!65eUmyV0g~MzUT(qeSq!pQzKKK^lR)dk#vN%G~euBEe0&OH1L(gcT!NmjpyxaB#M< zqPxNXjF?oEGHf$JEQ=0(?l&)JZY?gZFp%E!i&pKkI6*z+QJve)tb9c?h_w2o4bjf zXUj(*G)QB+pUkT&g#7!kRpqOwEDo7hVSv`>WDYu5bJWpq&Mpq_w{qVleg09obAc7u zGaVjOn3lv4mXuy`#Wd$~qYe3Fq@iE{4zX*MshMw~<^T|1`28LN7r+%<`A1w?T{TYX z_mN-0x%z+(%V1kLFcoBHHh`L($GI9<;gpGiGD+B@^Z9bf>CMQBxeSa6Tw{rtb3zaz zaR1*Ag)11OKOt`ie@Dl+!KXKyit}M`7Y+&AYsNI>`xF@GA{A$boUWu*t)hpw- z8@cv~Q+^rcT$H!8`0X8%(Xmth!ltpk@#+cjh(d0=t=X{r>^ga9v}P%_SP72rwgi{9 z!QD!{{~f*4SCfMWTnkyA#QTRpYx+p*^;W*Hu#-sir;4Q0l>tTP-}L)lYO|%N4^^$c zUc5lKHdyl@;^3V5Ub+?!G4DUUEOU}?Yh>y|*u10$!qJ=GcFGTM;kgl&m*oTPRv(z8{f4<% zu2aZE^ou=#cdl-SF0vD{cJo^vEwZz^J`_N4|LgfyZ`B!TpWHfSwfuy5UoF9b1H7d7 z0Dqs85CkK|I?}){c34|7yAnf`hf@4KwGxLhw@(LccYf6c<(OXtW#C!?U|KCxt?{9NUOwPEK|{h4d`B zVOcSgc&_Q1!|Yz7ShY7phmfE44~hb_oxeH_(V_=SSKZsEef@P&WD$1eW~i9ck~A58@UQCzOFQkAJ%R{X z+^7*jFWi49ap2Nk^RSxA_+tw{yQ(=vWc=OoTIox>MD;dqwUz)$ZJ&3=h9x z=~*khXeIFb1&khKR6xV~6_-KLU4(D1TE54(-`Rpwj{wPoq&I7ZztvRXACO%fpFh&;2+GjHIa$BqkJ* zc3_tV7&?q|5CmipsGwH?fC2IJ1xS4LpCM6n9-5@*k8Tqr?XfVjfZ;3GU$mG^faGNk z>7Pid>57O<@#@5oouP06cTv?*#rJ=^QhMn?(rr~gVEW!7+APmlu{8w}2 zyu9Wlu^^SO7jvDI(|GY*Dh=T{nLncb;7STUssvBDQNC63OU{B87z??}>)|YQQ)!4- zqStFEWk!-pGN-A{-ou+$0*yi_@|RI?VpScJTqj*RQLDcz%&u=fP6+72=y z@T{>&=LB=ML_Tu0P4)frdA`dJ4O32uzwSIAfP$fKT2zAkmdXt}8$DpD`EED=;CG6} zg~P%tbJScj?L)_TV4`kky@9+jnVxoaarIjH>L+Z|90ph;X9x=?_S)so-j%g*XSdrY ziou~$B_dYl?h>~u)KkVT0^*^zbQcJw+sR{GMlW*|8KI+VHYLCq7|?}$y&~M(dqvcf?_UfQDkrD%NS4GhPf%(Z|wK{Cr(`Zfn?o>5_th zZ4Z@)cXdAC^~8-8MZn0!)Ch55WMnMk)D$1Y#zOmOxMJQ5rdpeX_Qdwy|qeF@?Nk15;@4edBR#3W!o@L25h4sOB z1DnWD z0I929eM3Ww+Yaa8_W@G7IMdZj%UumT0TH$!15);tZ%OX4C@%aBnNj^Wh0h_H_ibAB zzR!F-9H}fPH{ST0v#Yy%;-|wzQUBJ)n;9;=-i;gSi|qC&U0_^db3%N0>l>bNJZSiC zZ;DquxjH2!rRi}}`wJzJa_644diSGGs5;s;+Z|eYDs3GdQ7YThZy#1VNlxuzO+(p0 z>_TSxE9O7-cJHdX*Rz&-+FmM8Z96=WFDl?Douuw;Z;wC3^5X{T?Y{gPSLAP;fJd?9+)zv6&?&+xF6&-#3) z?c=EzTDJP{AMg>}Z$?-{+4$bQ+TBLQZjDKOCae7r0;vbfoz402@#}uo9v*h{x{dh zG}RT-+08FBlJSzs$1)FV8MA^^b`)s7;Qeqp(2>(V(*`@zqp4oIM8(g}q~~&Pdlme1 zp+p=PPED*BOT1v3Pva``B9puuuS|Z`d_9-vzt(i$_%dg{AS8eEiR)n5{hck4YGL&9 zPX;^w3QEva1lU_0wFutPkd4lm9Zu!|ruRV3RSLD9%kEDGFa8QeAd3wc$Ik8>^EbY| zmov_GAgkJEgG8Y=8tTqWKN2uKzKbypxUOz_K{C161(mvAs~}>f3`(q6<$We5CNq^g zIkF&DnM}=2uv^cFSv{B+i90sWGD=oTG;A}9j);sLUM`Lqv03=D*H{t_;yEJfZB99- zafE1Jq0|*a6BW}q&g7*NwiLzb50r~f+Hv_yM@B{_{CQ~OvN!e0NoD0YT7u(Anr(=d zh~Crtrf2SkXlwrb^-DC-P#2lSf5FL}X8#IOO2q0DlSx1_lcF z7@50)ea1{^;QM>!n?Qo;QL~=fBWq&`&DSU9%iWv(St~uUJ^lTe&THF5qPot`xY@a- z*yB`4<7InHM# zB_(gTgcXwKGHkHMGqzPtGbwJ9)VGDt$+J^x8wE*`ze?}e^?&MO>a;ZEb&$!-&%d!F z@X?-4P*7knvwY1mtHi_XfnZdVtHokNXw%OU=l(YnW3{YalnyARZ=98Bh-Z(MdYHK> zEKSi8jEer^k}P2;v50vM&P5MhgChX!V|>=}w>)>X=xZz!#0TtDi3{ z4w9iYD551yEK=LAedtL!g_`~>tiJg*OsYm~#^WOVZ?cpu`qxS1wYF!!SydspElrx8 zTY&tNG4Y5`5QQKl%6s9XExlx;tyynB8wiOVFRB-kwvXpVkKI zf)b2ay-5DJQ!pYDj*iN;F0HtiWnFHXM{_#8OO%#ib0T{_D#dMzssjNry}~9Ks(RScXr}V;T=>|@$pr?`ql2{O5fW0*9o<3z&_&m zjkboTe9G^#vkkVlw+|+?KA^m?E_GQWD{1R5F-}kIQibPLP^Q8lH6wNXumB$)0#U=@ z#(;>C>ejn-4fP2pMLD^TV;hj{2ktOz?so#lrX4J2?xx|O89#7;M%1VWm!ZmH|2`}) zi-rI>d7-nGM8XjRSyVG}Yam`ti-VQLDhdH%kh3q8=2Hb)+$LLiww$e#51+kkTJu=k zlg_jsB^^wnzAuSeM~8`w(x!oe9DQ@QWXEq$`BulD8C6iqvU*TC9$r~RH1_XNg437x zF7Jgpw(pB_`2<1@h?EbkK4t;q8FFo?Ee}xD1jM4BKYt#js4Mipja03eAM{W%eTgj?IEpvJ0yAFT^t3da zJPZ+5i!3|6YDj#8qGAdsPaaj<9A&ZfQGs`c(gFoNIrA&OGuZ5f(ZKviPtPGfKE8@( zL1*La z>}=Rby3=2z1KnlJ6xtYZ1~K~YuTHh4{QJNhTH|tN`^*LZ+O5_*u_|8jb=0-BHA!={ zy(shp^Vnf(O`Hj;{S>uIJo2q0A91Xk0^MaW4S`z;{D6$U3YC(%>Q79OvQ@3C!;Xz) zvRMhU{pD)tqHbZ;C#fh{h%t`e-hQCuKmXiJ1SQT_U=n&$BF&kP_<6w}Zth2`D?mwR zNFczXXsjr2*ck^E$tTSGA05vU_J5nsvcVR(ZUkfj;<_490s~AB@`g1+A-FbN<7Yll zT<#hAGHYlLgVc7`Nl?KCl9G~aGBD~#a4=$k0iWqn(fr!=r1@W^QXO4r7f*+|#l?FQ zpzb$4YzkOFdI9d5k6}O@AAlTg(FE%CFSdqmN*u~G)qi?GNqA>{fnmJ!lgrvei(+PC zriB@n`1C2W`3lYBkZDQq36c_qK4;)P&~?OmoID22LBGjMEQkD1JSya4sbV?n2D3s;cDg-@m{9!FzpZTVKB?s`;Yi+EP7590*Ocfa(RE>y%qlL{q3O2?jQ|~@t;|~-CUuW znMZDU2mvTGsiDavsy{z!dT!2owU&jEvEk(@9}Wu|Q@B?|f_abEpm+Kz80oQEgBp}E zHc9{FjKG~EwnyHf(=KxXV_^l0^6Zl=>a+@_C$dx|=nMEzKW{7RL00U1^?_G zdc4RAW*qiG2dnceF?>zH!^JL^tkP@CEVWL}c1#+7d{bibr_gzVBL?J5TS(PPl=^LV zeedoT9VSmIz>v!5@#DuiEVSJ>otzlH7?*N!&Be!a_6l?vWVKxc17C&Ja~hP)?|FEb z>gws;>H8di`ry0jYTJs63dCr|ggkS2Y?XQ+*EgwVc z<&r2a5|!chU`2P=H>|TBG*mD6zzNdoxs7a=Q{(*(K8?*=c)ciQ12j1pTDlp_yGsg} zPAti+O)|;U%;h^IoXFM~GABM;Uu=EJ@$uBRqBS z%QOw8eMo4AbaZreHTI(anNjmw?iR0lH<^0HXm?sqHzF4JLyTi!FkN`~hnW+!fBH)l zofdkKHAUre4F5Kp_1w|a{#ZJ)h>nvKA_he~uZEQ)&2_?7j z;6EE1&A%hn7Jv2Tn(~3z@O+E*zOzT1r43v7K-AJQGY~w1tJB%a3boJ14nq9142?7KW;QDD7HW zT1fB+A98)h_D!~CFlf1>XIC{JI(_Ic4tel>%``!tvI@0Z5qAR2^z@#1+`IShQ1oJN z3eiPqkk(Wf$u2(DE{R*MUa4G)x89h)w6*_yJdCP!b@iqKKf#A|cw}Vr=4RPC%TEXi zgcY$3wzjqk12a=aR(4oW<}|wGb6L=s?W!`Q``=I*dCQ-M067m7JluGtNnNRL74@@& z34Y;*$8wUdHw3w{sQ2g>&ky=AC9Ngbpw zY1?rb-Eqs@u$PS<$>Y}*1Vqojac$ui0c@WC8xEi79eUe#Q%f@0b7BSL7c#+Q-AMQe z=_ZT`lBfA!vxxrL-W-`Z()9tptq^Ul;9w4I=D1bAQRCCv+OS$) z-}L~1ZTJpaJ;(EE113IMvR4!wXw;RIQLuVpShb#HD4@J}?F;~|l4={iR>ODZm;qZL z4PoHBN7;`%p1LT?a4ng^1O-@)-?pE!TOFtk9wbqP>9B+znw=*GDRit`Vi-ohC7(yI z==8j$cDE?!HJ+^!<{B8Gy$~)vsJ1nzPZ{jksQKmrb8_l1m|!qcIl{HwWW5Q?zSgJ6 zQdjN+F)t4L*VLWg0?k2(UtX01fKlFWzAT*jJFn>RE?>#;Zdfokb*>z*ItY)+1prZMGBeb-Se z!QP55Z*;8obn2xDgu=b}qBU~`U6|hdWW0ZmftFlWCF3x?DPS$+9IvEmfG*xXf)Jx6M(+D^0}$8 z+A3o0$z1F@ix;wIF9ryf!R47uLD&3p;u-KE;cD~+rpWiOhB~Bd;dZNP2Pq7=ynwx? z9d$F4rH9x@-`Jt%HY)%28X~o~AG&PO2IbisrrVZG(G54;+_&zPI?{lF2Bo^;8(h93 zEa)OtP&Pv@Sx6S?>N@Pvivq@n?VkJOK=7;vJ!6|K*kDy5Ljx*45jKr+1l2xz2a?AXVx~;2IN)f&Z*j{i|Du6flB<7^W}^rvrwl zhcwK>E?_Q1_)a9|X7Bvx%SG7B)0S`TB`yM`=W#CVJ3$kcKJxde9k^6WG3TU16H1+H zHp8T8N30Qr=wAuTFOX&h1MSbpnhXKZmLF&bEUImIrVkt`VaBdkimvCJ3o|PAAk} zyDJD{Z=85ldyi-Ysa@f;vOf(leu)JjM*=ENI0OD}IN;7e)SF!4SsL0P1UrEV!-kN@ zZoRd#x`5HkA6A5sX4Tev8ek-^_5g-J;D-Ig6AZ6!2w$f21bt5!@LM>5stX3d8a6|) zGw&-~wg`qs|8Ea=;>#hS1Hu?V`?RU+SclOL1DnlkEm#*rx@gPghEiI&YT7aYFj4Z_ zaWm+;d~U@Ziy{jDyL~%a8W1sUDgOSRyH?4`>-U3zy$me5s3G*p$qpgkEy;7SE32VM zcx?{}1N)(WRq`F6E%^%R#m^RvbbST~=j;jk)qfeJ@-i67=E8k45cy-Z6%+|BpHmdI YcW#Lsi; { const makeFullUrlIfNeeded = (url) => { - // Return early if url is undefined or empty - if (!url) { - setOldUploadedFileUrl(''); - setFileExtension(''); - setImageName(''); - return; + if(!url){ + return } - const isHttp = url.startsWith('http://') || url.startsWith('https://'); if (!isHttp) { setOldUploadedFileUrl(`${IMAGE_LOCATION_BASE_URL}${url}`); @@ -114,16 +109,50 @@ const CustomFileUpload = forwardRef(function CustomFileUpload( if (!value) { return; } - let formData = new FormData(); - formData.append('file', value); - formData.append('fileName', value?.name); + const filePayload = { + folder: "assests", + file_name: value.name, + }; try { setIsLoading(true); - const data = await fileUpload(formData); - onUploadDone(documentName, data?.data?.data?.Key); + const response = await getPresignedUrl(filePayload); + + // Debug the response structure + console.log('API Response:', response); + + // Check if we have a valid response with the expected structure + if (response?.data?.data?.Key) { + // Use the Key from the response + onUploadDone(documentName, response.data.data.Key); + await uploadToS3(value, response.data.data.api_url); + } else { + // If the expected structure is not found, try to find the key in a different location + // or use a fallback value + console.log('Response structure is different than expected'); + + // Try different possible paths to find the key + const key = response?.data?.Key || + response?.data?.data?.key || + response?.Key || + value.name; // Fallback to the file name if key not found + + console.log('Using key:', key); + onUploadDone(documentName, key); + + // Try to find the API URL similarly + const apiUrl = response?.data?.data?.api_url || + response?.data?.api_url || + response?.api_url; + + if (apiUrl) { + await uploadToS3(value, apiUrl); + } else { + console.error('Could not find API URL in response'); + } + } return; } catch (error) { - // console.error(error); + console.error('Error in handleFileUpload:', error); pushNotification('Error while uploading file', NOTIFICATION.ERROR); } finally { setIsLoading(false); diff --git a/src/components/ImagePreviewComponent.jsx b/src/components/ImagePreviewComponent.jsx new file mode 100644 index 0000000..0bc067f --- /dev/null +++ b/src/components/ImagePreviewComponent.jsx @@ -0,0 +1,114 @@ +import { Box, Button, Grid } from '@mui/material'; +import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; +import React from 'react'; +import { useStyles } from './styles/ImagePreviewComponentStyles'; +import AddOutlinedIcon from '@mui/icons-material/AddOutlined'; +import RemoveOutlinedIcon from '@mui/icons-material/RemoveOutlined'; +import ZoomOutOutlinedIcon from '@mui/icons-material/ZoomOutOutlined'; +import { FILE_EXTENTIONS_ICONS } from '../constants'; + +const ImagePreviewControls = ({ zoomIn, zoomOut, resetTransform }) => { + const classes = useStyles(); + + return ( + + + + + + ); +}; + +const ImagePreviewComponent = ({ + fileUrl, + fileExtension, + handleDownload, + ref, +}) => { + const classes = useStyles(); + return ( + <> + {fileExtension === 'pdf' ? ( + <> + + Uploaded file + + + + ) : fileExtension === 'doc' || + fileExtension === 'docx' || + fileExtension === + 'vnd.openxmlformats-officedocument.wordprocessingml.document' || + fileExtension === 'msword' ? ( + <> + + Uploaded file + + + + ) : ( + + {(utils) => ( + + + + Uploaded file + + + + + )} + + )} + + ); +}; + +export default ImagePreviewComponent; diff --git a/src/components/styles/ImagePreviewComponentStyles.js b/src/components/styles/ImagePreviewComponentStyles.js new file mode 100644 index 0000000..41a0f58 --- /dev/null +++ b/src/components/styles/ImagePreviewComponentStyles.js @@ -0,0 +1,55 @@ +import makeStyles from '@mui/styles/makeStyles'; +import { pxToRem } from '../../theme/typography'; + +export const useStyles = makeStyles((theme) => ({ + pdfAndDocPreviewBox: { + minHeight: '450px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'column', + }, + pdfAndDocPreviewDownloadButton: { + marginTop: theme.spacing(2), + fontFamily: theme?.fontFamily?.regular, + }, + transFormImageBox: { + minHeight: '65vh', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + customTransFormWrapper: { + overflow: 'visible', + }, + iconSize: { + fontSize: pxToRem(28), + }, + controlButtons: { + marginTop: theme.spacing(1), + }, + controlButton: { + color: theme.palette.common.white, + cursor: 'pointer', + border: 'none', + borderRadius: theme.spacing(0), + padding: theme.spacing(1.2), + '&:hover': { + backgroundColor: 'rgba(0,0,0,0.9)', + color: theme.palette.common.white, + }, + }, + zoomOutButton: { + backgroundColor: 'rgba(0,0,0,0.7)', + borderTopLeftRadius: theme.spacing(0.8), + borderBottomLeftRadius: theme.spacing(0.8), + }, + zoomResetButton: { + backgroundColor: 'rgba(0,0,0,0.4)', + }, + zoomInButton: { + backgroundColor: 'rgba(0,0,0,0.7)', + borderTopRightRadius: theme.spacing(0.8), + borderBottomRightRadius: theme.spacing(0.8), + }, +})); diff --git a/src/config/api.js b/src/config/api.js index bf7e744..2ac1711 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -7,22 +7,22 @@ import store from '../redux/store'; export const axiosInstance = axios.create({ baseURL: API_BASE_URL, - crossDomain: true, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*', - }, - withCredentials: true, - timeout: 300000, + // crossDomain: true, + // headers: { + // 'Content-Type': 'application/json', + // // 'Access-Control-Allow-Origin': '*', + // // 'Access-Control-Allow-Headers': '*', + // }, + // withCredentials: false, + // timeout: 300000, }); axiosInstance.interceptors.request.use( async function (config) { try { const token = JSON.parse(localStorage.getItem('redux')); - if (token?.data?.data) { - config.headers.Authorization = `${token?.data?.data}`; + if (token?.login?.token) { + config.headers.Authorization = `Bearer ${token?.login?.token}`; } const state = store.getState(); const companyId = state?.loginAsCompanyAdmin?.companyId; // Extract companyId @@ -47,7 +47,7 @@ axiosInstance.interceptors.request.use( axiosInstance.interceptors.response.use( function (response) { - if (response?.data && response?.data?.error !== '') { + if (response?.data && response?.data?.error !== null) { pushNotification(response?.data?.message, NOTIFICATION.ERROR); return Promise.reject(response); } diff --git a/src/config/firebase.js b/src/config/firebase.js index f5e3680..d107e43 100644 --- a/src/config/firebase.js +++ b/src/config/firebase.js @@ -1,5 +1,5 @@ import { initializeApp } from 'firebase/app'; -import { getMessaging, onMessage } from 'firebase/messaging'; +import { getMessaging, onMessage, isSupported } from 'firebase/messaging'; export const firebaseConfig = { apiKey: "AIzaSyDBDwlnQsbIxKni_UzZxDjeIk0akK-vDPM", @@ -12,7 +12,35 @@ export const firebaseConfig = { const firebaseApp = initializeApp(firebaseConfig); export default firebaseApp; -export const messaging = getMessaging(firebaseApp); -export const onForegroundMessage = () => - new Promise((resolve) => onMessage(messaging, (payload) => resolve(payload))); +// Initialize messaging only if supported +let messagingInstance = null; + +// Check if messaging is supported before initializing +export const initializeMessaging = async () => { + try { + const isSupportedBrowser = await isSupported(); + if (isSupportedBrowser) { + messagingInstance = getMessaging(firebaseApp); + return messagingInstance; + } else { + console.log('Firebase messaging is not supported in this browser'); + return null; + } + } catch (error) { + console.error('Error checking messaging support:', error); + return null; + } +}; + +// Safe getter for messaging +export const getMessagingInstance = () => messagingInstance; + +// Safe onForegroundMessage function +export const onForegroundMessage = async () => { + const messaging = await initializeMessaging(); + if (!messaging) { + return Promise.resolve(null); // Return resolved promise with null if messaging not supported + } + return new Promise((resolve) => onMessage(messaging, (payload) => resolve(payload))); +}; diff --git a/src/constants/index.js b/src/constants/index.js index abd74be..a59b301 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -5,6 +5,12 @@ export const NOTIFICATION = { ERROR: 'error', }; + +export const USER_ROLES = { + SUPER_ADMIN: 'SUPER_ADMIN', + CLINIC_ADMIN: 'CLINIC_ADMIN', +} + export const CLINIC_TYPE = { UNREGISTERED: 'UNREGISTERED', REGISTERED: 'REGISTERED', @@ -28,6 +34,8 @@ export const PERMISSIONS = { }; export const CLINIC_STATUS = { + UNDER_REVIEW:"UNDER_REVIEW", + ACTIVE:"ACTIVE", ON_HOLD: 'ON_HOLD', REJECTED: 'REJECTED', NOT_REVIEWED: 'NOT_REVIEWED', @@ -145,6 +153,10 @@ export const numRegex = /^[0-9\b]+$/; import PdfIcon from '../assets/images/icon/pdf.png'; import DocIcon from '../assets/images/icon/doc.png'; +import JpegIcon from '../assets/images/icon/jpeg.png'; +import JpgIcon from '../assets/images/icon/jpg.png'; +import PngIcon from '../assets/images/icon/png.png'; +import SvgIcon from '../assets/images/icon/svg.png'; export const FILE_EXTENTIONS_ICONS = { pdf: PdfIcon, @@ -152,6 +164,10 @@ export const FILE_EXTENTIONS_ICONS = { docx: DocIcon, 'vnd.openxmlformats-officedocument.wordprocessingml.document': DocIcon, msword: DocIcon, + jpeg: JpegIcon, + jpg: JpgIcon, + png: PngIcon, + svg: SvgIcon, }; export const DEBOUNCE_DELAY = 500; diff --git a/src/context/FirebaseProvider.jsx b/src/context/FirebaseProvider.jsx index e3ee572..e072946 100644 --- a/src/context/FirebaseProvider.jsx +++ b/src/context/FirebaseProvider.jsx @@ -1,7 +1,7 @@ import { onMessage } from 'firebase/messaging'; import { createContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { messaging } from '../config/firebase'; +import { initializeMessaging, getMessagingInstance } from '../config/firebase'; import { redirectByNotificationType } from '../views/Notifications/notificationUtils'; // Create the context @@ -12,8 +12,15 @@ const FirebaseProvider = (props) => { const navigate = useNavigate(); // Initialize navigation without page reload useEffect(() => { - checkNewMessage(messaging); - }, [messaging]); + const setupMessaging = async () => { + const messagingInstance = await initializeMessaging(); + if (messagingInstance) { + checkNewMessage(messagingInstance); + } + }; + + setupMessaging(); + }, []); const showBrowserNotifications = (payload) => { // Handle notification click diff --git a/src/layouts/mainLayout/components/Header.jsx b/src/layouts/mainLayout/components/Header.jsx index 35d462e..ea47174 100644 --- a/src/layouts/mainLayout/components/Header.jsx +++ b/src/layouts/mainLayout/components/Header.jsx @@ -4,7 +4,7 @@ import ProfileSection from './ProfileSection'; import { useStyles } from '../mainLayoutStyles'; import NotificationSection from './NotificationSection '; import { useSelector } from 'react-redux'; -import { CLINIC_STATUS } from '../../../constants'; +import { CLINIC_STATUS, USER_ROLES } from '../../../constants'; function Header() { const theme = useTheme(); @@ -21,7 +21,7 @@ function Header() { } > - {user?.isBsAdmin ? null : `Welcome to ${user?.company?.name}`} + {user?.userType == USER_ROLES.SUPER_ADMIN.toLowerCase() ? null : `Welcome to ${user?.created_clinics?.[0]?.name}`} diff --git a/src/layouts/mainLayout/components/ProfileSection.jsx b/src/layouts/mainLayout/components/ProfileSection.jsx index 29b8d19..d9e7971 100644 --- a/src/layouts/mainLayout/components/ProfileSection.jsx +++ b/src/layouts/mainLayout/components/ProfileSection.jsx @@ -16,6 +16,7 @@ import { useTheme } from '@mui/material/styles'; // assets import signoutImg from '../../../assets/images/icon/signout.svg'; +import phoneImg from '../../../assets/images/icon/phone.svg'; import { useStyles } from '../mainLayoutStyles'; import MainCard from './MainCard'; @@ -38,7 +39,7 @@ const ProfileSection = () => { const anchorRef = useRef(null); const user = useSelector((state) => state?.login?.user); - const isBsAdmin = user?.isBsAdmin; + const isSuperAdmin = user?.isSuperAdmin; const companyStatus = useSelector( (state) => state?.login?.user?.company?.status @@ -66,7 +67,8 @@ const ProfileSection = () => { const menuItems = [ - { id: 5, img: signoutImg, text: 'Sign Out', alt: 'signoutImg' }, + { id: 1, img: phoneImg, text: 'Contact Us', alt: 'contactUsImg' }, + { id: 2, img: signoutImg, text: 'Sign Out', alt: 'signoutImg' }, ].filter(Boolean); const renderProfile = (item, index) => ( @@ -76,6 +78,7 @@ const ProfileSection = () => { handleMenuItemClick(item)}> {item.alt} { const handleMenuItemClick = (item) => { switch (item.id) { case 1: - navigate('/profile-settings'); + // navigate('/contact-us'); break; case 2: - navigate(`/profile`); + commonLogoutFunc(); break; case 3: // setShowTransactionHistoryPopup(true); diff --git a/src/layouts/mainLayout/components/Sidebar.jsx b/src/layouts/mainLayout/components/Sidebar.jsx index 56ef306..dc9fd49 100644 --- a/src/layouts/mainLayout/components/Sidebar.jsx +++ b/src/layouts/mainLayout/components/Sidebar.jsx @@ -34,7 +34,7 @@ import { SIDEBAR_CONFIG } from "./sideBarConfig"; // Adjust path if necessary const Sidebar = ({ onClose, showCloseIcon }) => { const classes = useStyles(); const location = useLocation(); - const { isBSAdmin } = isBSPortal(); + const { isSuperAdmin } = isBSPortal(); const [activeLink, setActiveLink] = useState("Dashboard"); const [accordianActiveLink, setAccordianActiveLink] = useState("All Jobs"); const [parentRoute, setParentRoute] = useState(""); @@ -92,13 +92,13 @@ const Sidebar = ({ onClose, showCloseIcon }) => { (companyStatus === CLINIC_STATUS.APPROVED && HIDE_FUNCTIONALITY && HIDE_MODULES.includes(item?.path)) || - (isBSAdmin && HIDE_FUNCTIONALITY && HIDE_MODULES.includes(item?.path)); + (isSuperAdmin && HIDE_FUNCTIONALITY && HIDE_MODULES.includes(item?.path)); // Only render if user has the required role if (hasRole) { // Determine if the link should be disabled const isDisabled = - (!isBSAdmin && companyStatus !== CLINIC_STATUS.APPROVED) || hideFeature; + (!isSuperAdmin && companyStatus !== CLINIC_STATUS.APPROVED) || hideFeature; const targetPath = isDisabled ? "#" : `/${item.path}`; const isActive = activeLink === item.path; // Check if this link is the active one @@ -239,7 +239,7 @@ const Sidebar = ({ onClose, showCloseIcon }) => { {visibleChildren.map((subItem, subIndex) => { // Determine if the sub-item link should be disabled const isSubDisabled = - !isBSAdmin && companyStatus !== CLINIC_STATUS.APPROVED; // Add hideFeature logic if needed for sub-items + !isSuperAdmin && companyStatus !== CLINIC_STATUS.APPROVED; // Add hideFeature logic if needed for sub-items const subTargetPath = isSubDisabled ? "#" : `/${subItem.path}`; const isSubActive = combinedRoute === subItem.path; // Check if this child link is active diff --git a/src/layouts/mainLayout/components/sideBarConfig.js b/src/layouts/mainLayout/components/sideBarConfig.js index b4c78b7..4284e25 100644 --- a/src/layouts/mainLayout/components/sideBarConfig.js +++ b/src/layouts/mainLayout/components/sideBarConfig.js @@ -9,10 +9,9 @@ import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'; import SettingsIcon from '@mui/icons-material/Settings'; import ArticleIcon from '@mui/icons-material/Article'; import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined'; -import TopicIcon from '@mui/icons-material/Topic'; -import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined'; - -import { USER_ROLES } from '../../../redux/userRoleSlice'; +import PaymentIcon from '@mui/icons-material/Payment'; +import PaymentOutlinedIcon from '@mui/icons-material/PaymentOutlined'; +import { USER_ROLES } from '../../../constants'; // Define the sidebar configuration with proper permission fields export const SIDEBAR_CONFIG = [ @@ -48,6 +47,14 @@ export const SIDEBAR_CONFIG = [ // Only super admin can access admin staff management roles: [USER_ROLES.SUPER_ADMIN] }, + { + text: 'Payment Management', + path: 'payment-management', + icon: PaymentOutlinedIcon, + activeIcon: PaymentIcon, + // Only super admin can access payment management + roles: [USER_ROLES.SUPER_ADMIN] + }, { text: 'Doctor/Nurse Management', path: 'doctor', diff --git a/src/redux/mockUserData.js b/src/redux/mockUserData.js index e06750f..a2a4436 100644 --- a/src/redux/mockUserData.js +++ b/src/redux/mockUserData.js @@ -3,7 +3,7 @@ export const mockSuperAdmin = { id: 1, name: "Super Admin User", email: "superadmin@example.com", - isBsAdmin: true, + isSuperAdmin: true, isAdmin: true, permissions: ["SUPER_ADMIN_PERMISSION"], roles: [ @@ -25,7 +25,7 @@ export const mockClinicAdmin = { id: 2, name: "Clinic Admin User", email: "clinicadmin@example.com", - isBsAdmin: false, + isSuperAdmin: false, isAdmin: true, permissions: ["CLINIC_ADMIN_PERMISSION"], roles: [ diff --git a/src/redux/setMockUser.js b/src/redux/setMockUser.js index 9854541..f3fb216 100644 --- a/src/redux/setMockUser.js +++ b/src/redux/setMockUser.js @@ -25,7 +25,7 @@ export const setMockUser = (userType) => { }; // Export mock user types for convenience -export const MOCK_USER_TYPES = { +export const MOCK_USER_ROLES = { SUPER_ADMIN: 'superAdmin', CLINIC_ADMIN: 'clinicAdmin' }; diff --git a/src/redux/userRoleSlice.js b/src/redux/userRoleSlice.js index a9c47de..6898a07 100644 --- a/src/redux/userRoleSlice.js +++ b/src/redux/userRoleSlice.js @@ -1,9 +1,3 @@ -// User role constants -export const USER_ROLES = { - SUPER_ADMIN: 'SUPER_ADMIN', - CLINIC_ADMIN: 'CLINIC_ADMIN', -}; - // Initial state const initialState = { role: null, diff --git a/src/routes/index.js b/src/routes/index.js index a98df84..5d15106 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -12,6 +12,7 @@ import ClinicSetup from "../views/ClinicSetup"; import ClinicTranscripts from "../views/ClinicTranscripts"; import ContractManagement from "../views/ContractManagement"; import MasterDataManagement from "../views/MasterData"; +import PaymentManagement from "../views/PaymentManagement"; export const routesData = [ { @@ -26,6 +27,7 @@ export const routesData = [ { path: "/clinicSetup", component: ClinicSetup }, { path: "/transcripts", component: ClinicTranscripts }, { path: "/masterData", component: MasterDataManagement }, + { path: "/payment-management", component: PaymentManagement }, ], isProtected: true, }, diff --git a/src/routes/withPermission.jsx b/src/routes/withPermission.jsx index 029a684..caba52d 100644 --- a/src/routes/withPermission.jsx +++ b/src/routes/withPermission.jsx @@ -9,11 +9,11 @@ const withPermission = (Component) => (props) => { const companyStatus = useSelector( (state) => state?.login?.user?.company?.status ); - const { isBSAdmin } = false; - // const { isBSAdmin } = isBSPortal(); + const { isSuperAdmin } = false; + // const { isSuperAdmin } = isBSPortal(); // If the user is a BS Admin, render the component without any checks - if (isBSAdmin === true) { + if (isSuperAdmin === true) { return ; } diff --git a/src/services/auth.services.js b/src/services/auth.services.js new file mode 100644 index 0000000..e14c6f3 --- /dev/null +++ b/src/services/auth.services.js @@ -0,0 +1,11 @@ +import { axiosInstance } from "../config/api"; + +export const signup = (data) => { + const url = '/auth/register'; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); + }; diff --git a/src/services/clinics.service.js b/src/services/clinics.service.js index 243aecd..828ec29 100644 --- a/src/services/clinics.service.js +++ b/src/services/clinics.service.js @@ -1,45 +1,86 @@ +import { axiosInstance } from "../config/api"; import { CLINIC_TYPE } from "../constants"; import { clinicsData, registeredClinicsData } from "../mock/clinics"; +// export const getClinics = (params) => { +// switch (params.type) { +// case CLINIC_TYPE.UNREGISTERED: +// return { data: clinicsData }; +// case CLINIC_TYPE.REGISTERED: +// return { data: registeredClinicsData }; +// case CLINIC_TYPE.SUBSCRIBED: +// return { data: registeredClinicsData }; +// default: +// return { data: clinicsData }; +// } +// }; + export const getClinics = (params) => { - switch (params.type) { - case CLINIC_TYPE.UNREGISTERED: - return { data: clinicsData }; - case CLINIC_TYPE.REGISTERED: - return { data: registeredClinicsData }; - case CLINIC_TYPE.SUBSCRIBED: - return { data: registeredClinicsData }; - default: - return { data: clinicsData }; - } - }; + console.log(params); - export const getClinicsById = (id) => { - const url = `/companies/${id}`; - return new Promise((resolve, reject) => { - axiosInstance - .get(url) - .then((response) => resolve(response)) - .catch((err) => reject(err)); - }); - }; + let searchParams = new URLSearchParams(); + searchParams.append("size", params?.pagination?.pageSize ?? 10); + searchParams.append("page", params?.pagination.pageIndex ?? 0); + searchParams.append("filter_type", params?.type ?? CLINIC_TYPE.REGISTERED); + searchParams.append("search", params?.globalFilter ?? ""); - export const updateClinicStatus = (id, data) => { - const url = `/companies/${id}/company-review`; - return new Promise((resolve, reject) => { - axiosInstance - .post(url, data) - .then((response) => resolve(response)) - .catch((err) => reject(err)); - }); - }; + let url = `/clinics/?${searchParams.toString()}`; - export const getClinicsDashboardStatsById = () => { - const url = `/companies/dashboard-stats`; - return new Promise((resolve, reject) => { - axiosInstance - .get(url) - .then((response) => resolve(response)) - .catch((err) => reject(err)); - }); - }; \ No newline at end of file + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); + +}; + +export const getLatestClinicId = () => { + const url = `/clinics/latest-id`; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const getClinicsById = (id) => { + const url = `/clinics/${id}`; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const updateClinicStatus = (data) => { + const url = `/admin/clinic/status/`; + return new Promise((resolve, reject) => { + axiosInstance + .put(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const updateClinicDocs = (id, data) => { + const url = `/clinics/${id}`; + return new Promise((resolve, reject) => { + axiosInstance + .put(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const getClinicsDashboardStatsById = () => { + const url = `/companies/dashboard-stats`; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; diff --git a/src/services/dashboard.services.js b/src/services/dashboard.services.js new file mode 100644 index 0000000..6dc7718 --- /dev/null +++ b/src/services/dashboard.services.js @@ -0,0 +1,33 @@ +import { axiosInstance } from "../config/api"; + +export const getDashboardStats = () => { + const url = '/dashboard/'; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); + }; + + +export const getSignupMasterPricing = () => { + const url = '/dashboard/signup-pricing-master/'; + return new Promise((resolve, reject) => { + axiosInstance + .get(url) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); + }; + + +export const setSignupMasterPricing = (data) => { + const url = '/dashboard/signup-pricing-master/'; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); + }; \ No newline at end of file diff --git a/src/services/file.upload.services.js b/src/services/file.upload.services.js index 175d634..2d47982 100644 --- a/src/services/file.upload.services.js +++ b/src/services/file.upload.services.js @@ -23,3 +23,27 @@ export const fileUpload = (data, type = null, companyId = null) => { .catch((err) => reject(err)); }); }; + +export const getPresignedUrl = (data) => { + const url = `/s3`; + return new Promise((resolve, reject) => { + axiosInstance + .post(url, data) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +}; + +export const uploadToS3 = (file, putUrl) =>{ + const url = putUrl; + return new Promise((resolve, reject) => { + axiosInstance + .put(url, file, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then((response) => resolve(response)) + .catch((err) => reject(err)); + }); +} \ No newline at end of file diff --git a/src/services/jwt.services.js b/src/services/jwt.services.js new file mode 100644 index 0000000..89fc55d --- /dev/null +++ b/src/services/jwt.services.js @@ -0,0 +1,5 @@ +import { jwtDecode } from "jwt-decode"; + +export const decodeJWT = (token) => { + return jwtDecode(token); +} \ No newline at end of file diff --git a/src/utils/share.js b/src/utils/share.js index 32d65d5..139ca3d 100644 --- a/src/utils/share.js +++ b/src/utils/share.js @@ -1,6 +1,6 @@ import { logout } from "../services/users.service"; import { deleteToken } from "firebase/messaging"; -import { messaging } from "../config/firebase"; +import { getMessagingInstance } from "../config/firebase"; export const isLoggedIn = () => { let redux = JSON.parse(localStorage.getItem("redux")); @@ -27,8 +27,8 @@ export const resetLocalStorage = () => { export const isBSPortal = () => { let redux = JSON.parse(localStorage.getItem("redux")); - const isBSAdmin = redux?.login?.user?.isBsAdmin; - return { isBSAdmin }; + const isSuperAdmin = redux?.login?.user?.isSuperAdmin; + return { isSuperAdmin }; }; export const commonLogoutFunc = async (redirectPath = "/auth/login") => { diff --git a/src/views/ClinicDetails/component/FileEvaluate.jsx b/src/views/ClinicDetails/component/FileEvaluate.jsx index c6a1e27..2f8e27d 100644 --- a/src/views/ClinicDetails/component/FileEvaluate.jsx +++ b/src/views/ClinicDetails/component/FileEvaluate.jsx @@ -29,90 +29,78 @@ const FileEvaluate = ({ const [reviewedLogoFiles, setReviewedLogoFiles] = useState([]); const [notReviewedLogoFiles, setNotReviewedLogoFiles] = useState([]); const [reviewedOtherFiles, setReviewedOtherFiles] = useState([]); - const [notReviewedOtherFiles, setNotReviewedOtherFile] = useState([]); + const [notReviewedOtherFiles, setNotReviewedOtherFiles] = useState([]); - const getAscendingArray = (array) => { - const gstFiles = array.filter((file) => file.documentType === 'GST'); - const panFiles = array.filter((file) => file.documentType === 'PAN'); - const tanFiles = array.filter((file) => file.documentType === 'TAN'); - const filteredArray = []; - const maxLength = Math.max( - gstFiles.length, - panFiles.length, - tanFiles.length - ); + // const getAscendingArray = (array) => { + - for (let i = 0; i < maxLength; i++) { - if (gstFiles[i]) { - filteredArray.push(gstFiles[i]); - } - if (panFiles[i]) { - filteredArray.push(panFiles[i]); - } - if (tanFiles[i]) { - filteredArray.push(tanFiles[i]); - } - } - - return filteredArray; - }; + // return filteredArray; + // }; useEffect(() => { - // ...........reviewed logo file set............... - if (Array.isArray(files)) { - const filteredFiles = files.filter( - (file) => - file.documentType === 'LOGO' && - (file.status === CLINIC_DOCUMENT_STATUS.APPROVED || - file.status === CLINIC_DOCUMENT_STATUS.REJECTED) - ); - setReviewedLogoFiles(filteredFiles); - } - // .............no review logo files set............ - if (Array.isArray(files)) { - const filteredFiles = files.filter( - (file) => - file.documentType === 'LOGO' && - file.status === CLINIC_DOCUMENT_STATUS.NOT_REVIEWED - ); - setNotReviewedLogoFiles(filteredFiles); - } - // ..............reviewed other file set............ - if (Array.isArray(files)) { - const filteredFiles = files.filter( - (file) => - (file.documentType === 'PAN' || - file.documentType === 'TAN' || - file.documentType === 'GST') && - (file.status === CLINIC_DOCUMENT_STATUS.APPROVED || - file.status === CLINIC_DOCUMENT_STATUS.REJECTED) - ); - setReviewedOtherFiles(getAscendingArray(filteredFiles)); - } - // .............. no reviewed other file set........... - if (Array.isArray(files)) { - const filteredFiles = files.filter( - (file) => - (file.documentType === 'PAN' || - file.documentType === 'TAN' || - file.documentType === 'GST') && - file.status === CLINIC_DOCUMENT_STATUS.NOT_REVIEWED - ); - setNotReviewedOtherFile(getAscendingArray(filteredFiles)); - } + if (!Array.isArray(files)) return; + + // Process all files at once to avoid multiple iterations + const reviewedLogo = []; + const notReviewedLogo = []; + const reviewedOther = []; + const notReviewedOther = []; + + files.forEach(file => { + // Handle logo documents + if (file.logo_doc) { + if (file.logo_doc_is_verified) { + reviewedLogo.push({file: file.logo_doc, documentType: 'LOGO', isVerified: file.logo_doc_is_verified}); + } else { + notReviewedLogo.push({file: file.logo_doc, documentType: 'LOGO', isVerified: file.logo_doc_is_verified}); + } + } + + // Handle ABN and contract documents (excluding logo docs which are handled separately) + if (file.abn_doc ) { + if (file.abn_doc_is_verified) { + reviewedOther.push({file: file.abn_doc, documentType: 'ABN', isVerified: file.abn_doc_is_verified}); + } + else{ + notReviewedOther.push({file: file.abn_doc, documentType: 'ABN', isVerified: file.abn_doc_is_verified}); + } + } + + if (file.contract_doc) { + if (file.contract_doc_is_verified) { + reviewedOther.push({file: file.contract_doc, documentType: 'CONTRACT', isVerified: file.contract_doc_is_verified}); + } else{ + notReviewedOther.push({file: file.contract_doc, documentType: 'CONTRACT', isVerified: file.contract_doc_is_verified}); + } + } + }); + + // Update state with filtered files + setReviewedLogoFiles(reviewedLogo); + setNotReviewedLogoFiles(notReviewedLogo); + setReviewedOtherFiles(reviewedOther); + setNotReviewedOtherFiles(notReviewedOther); + + // Debug logs + console.log('Files processed:', files.length); + console.log('reviewedLogoFiles:', reviewedLogo); + console.log('notReviewedLogoFiles:', notReviewedLogo); + console.log('reviewedOtherFiles:', reviewedOther); + console.log('notReviewedOtherFiles:', notReviewedOther); }, [files]); // .........................get file name and extention function....................... const getFileNameUsingFile = (file) => { if (file) { - const url = new URL(file.fileURL); - return url.pathname.split('/').pop(); + // const url = new URL(file); + // return url.pathname.split('/').pop(); + return file } return; }; const getFileExtentionUsingFile = (file) => { if (file) { - const url = new URL(file.fileURL); + const url = new URL(file.file); const fileName = url.pathname.split('/').pop(); return fileName.split('.').pop(); } @@ -129,7 +117,7 @@ const FileEvaluate = ({ : CLINIC_DOCUMENT_STATUS.REJECTED, }; - if (file.documentType === 'LOGO') { + if (file.logo_doc) { const updatedFiles = [...notReviewedLogoFiles]; updatedFiles.splice(index, 1, newFile); setNotReviewedLogoFiles(updatedFiles); @@ -181,8 +169,12 @@ const FileEvaluate = ({ const handleDownload = (file) => { if (file) { const anchor = document.createElement('a'); - anchor.href = file.fileURL; - anchor.download = getFileNameUsingFile(file); + anchor.href = file.file; // file is now a direct URL + + // Extract filename from URL for download attribute + const fileName = file.file.split('/').pop(); + anchor.download = fileName; + anchor.click(); } }; @@ -200,7 +192,7 @@ const FileEvaluate = ({ return ( <> {/* ............CLINIC is Approved that time show only card............. */} - {companyStatus === CLINIC_STATUS.APPROVED ? ( + {companyStatus === CLINIC_STATUS.ACTIVE ? ( <> {reviewedLogoFiles.map((file, index) => ( @@ -217,10 +209,10 @@ const FileEvaluate = ({ CLINIC LOGO INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} {companyName} LOGO @@ -228,7 +220,7 @@ const FileEvaluate = ({ Uploaded File @@ -251,10 +243,10 @@ const FileEvaluate = ({ CLINIC {file.documentType} INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} {file.documentNumber} @@ -287,10 +279,10 @@ const FileEvaluate = ({ CLINIC LOGO INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} {companyName} LOGO @@ -341,10 +333,10 @@ const FileEvaluate = ({ CLINIC {file.documentType} INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} {companyName} LOGO @@ -494,17 +486,17 @@ const FileEvaluate = ({ CLINIC {file.documentType} INFO - + {/* Uploaded:{' '} {format(new Date(file?.updatedAt), 'dd MMM yyyy')} - + */} - {file.documentNumber} - - {file.documentNumber.length === GST_NUMBER_LENGTH && + */} + {/* {file.documentNumber.length === GST_NUMBER_LENGTH && file.documentType !== 'TAN' && file.documentType !== 'PAN' && ( - )} + )} */} @@ -632,17 +624,15 @@ const FileEvaluate = ({ - {getFileNameUsingFile(previewFile)} + {previewFile.split('/').pop()} handleDownload(previewFile)} classes={classes} /> diff --git a/src/views/ClinicDetails/component/GeneralInformation.jsx b/src/views/ClinicDetails/component/GeneralInformation.jsx index d8b8e4a..bdc7b22 100644 --- a/src/views/ClinicDetails/component/GeneralInformation.jsx +++ b/src/views/ClinicDetails/component/GeneralInformation.jsx @@ -9,13 +9,13 @@ import { profile } from '../../Login/loginAction'; import { setClinicId } from '../store/logInAsClinicAdminAction'; import { useStyles } from './styles/generalInformationStyles'; -const GeneralInformation = ({ companyData, companyAdminData }) => { +const GeneralInformation = ({ clinicData, clinicAdminData }) => { const dispatch = useDispatch(); const navigate = useNavigate(); const classes = useStyles(); const handleViewCompanyDashboard = async () => { - dispatch(setClinicId({ companyId: companyData?.id })); + dispatch(setClinicId({ companyId: clinicData?.id })); await dispatch(profile()); navigate('/'); }; @@ -32,13 +32,13 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { Uploaded:{' '} - {companyData?.updatedAt - ? format(new Date(companyData?.createdAt), 'dd MMM yyyy') + {clinicData?.updatedAt + ? format(new Date(clinicData?.createdAt), 'dd MMM yyyy') : ''} - {companyData?.status === CLINIC_STATUS.APPROVED && ( + {clinicData?.status === CLINIC_STATUS.APPROVED && ( { - Para Hills + {clinicData?.name}
Raised On:{' '} - {companyData?.requestRaisedOn - ? format(new Date(companyData?.requestRaisedOn), 'dd MMM yyyy') + {clinicData?.update_time + ? format(new Date(clinicData?.update_time), 'dd MMM yyyy') : NOT_AVAILABLE_TEXT}
@@ -74,17 +74,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { />
- Website: {companyData?.website} - -
- -
- - User ID: admin@gmail.com + {clinicData?.email}
@@ -93,7 +83,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyAdminData?.name} + {clinicAdminData?.name} User Name @@ -101,8 +91,8 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyAdminData?.designation - ? companyAdminData?.designation + {clinicAdminData?.designation + ? clinicAdminData?.designation : NOT_AVAILABLE_TEXT} @@ -111,8 +101,8 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyAdminData?.mobile - ? `+91-${companyAdminData.mobile}` + {clinicAdminData?.phone + ? `${clinicAdminData.phone}` : NOT_AVAILABLE_TEXT} @@ -121,7 +111,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyData?.pinCode ? companyData?.pinCode : NOT_AVAILABLE_TEXT} + {clinicData?.postal_code ? clinicData?.postal_code : NOT_AVAILABLE_TEXT} Pincode @@ -133,7 +123,7 @@ const GeneralInformation = ({ companyData, companyAdminData }) => { - {companyData?.street ? companyData?.street : NOT_AVAILABLE_TEXT} + {clinicData?.address ? clinicData?.address : NOT_AVAILABLE_TEXT} Full Address diff --git a/src/views/ClinicDetails/index.jsx b/src/views/ClinicDetails/index.jsx index e9810fa..7d14ecb 100644 --- a/src/views/ClinicDetails/index.jsx +++ b/src/views/ClinicDetails/index.jsx @@ -1,6 +1,6 @@ -import CloseIcon from '@mui/icons-material/Close'; -import DoneIcon from '@mui/icons-material/Done'; -import { LoadingButton } from '@mui/lab'; +import CloseIcon from "@mui/icons-material/Close"; +import DoneIcon from "@mui/icons-material/Done"; +import { LoadingButton } from "@mui/lab"; import { Box, Button, @@ -10,30 +10,31 @@ import { Tab, TextField, Typography, -} from '@mui/material'; -import { useFormik } from 'formik'; -import React, { useEffect, useRef, useState } from 'react'; -import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import onHoldDisableIcon from '../../assets/images/icon/onHoldDisable.svg'; -import onHoldEnableIcon from '../../assets/images/icon/onHoldEnable.svg'; -import CustomBreadcrumbs from '../../components/CustomBreadcrumbs'; -import Loader from '../../components/Loader'; -import PageHeader from '../../components/PageHeader'; +} from "@mui/material"; +import { useFormik } from "formik"; +import React, { useEffect, useRef, useState } from "react"; +import { Link, useLocation, useNavigate, useParams } from "react-router-dom"; +import * as Yup from "yup"; +import onHoldDisableIcon from "../../assets/images/icon/onHoldDisable.svg"; +import onHoldEnableIcon from "../../assets/images/icon/onHoldEnable.svg"; +import CustomBreadcrumbs from "../../components/CustomBreadcrumbs"; +import Loader from "../../components/Loader"; +import PageHeader from "../../components/PageHeader"; import { CLINIC_DOCUMENT_STATUS, CLINIC_STATUS, NOTIFICATION, -} from '../../constants'; +} from "../../constants"; import { getClinicsById, updateClinicStatus, -} from '../../services/clinics.service'; -import { pushNotification } from '../../utils/notification'; -import CustomModal from '../Modal/Modal'; -import { useStyles } from './clinicDetailsStyles'; +} from "../../services/clinics.service"; +import { pushNotification } from "../../utils/notification"; +import CustomModal from "../Modal/Modal"; +import { useStyles } from "./clinicDetailsStyles"; +import FileEvaluate from "./component/FileEvaluate"; -import GeneralInformation from './component/GeneralInformation'; +import GeneralInformation from "./component/GeneralInformation"; function ClinicDetails() { const classes = useStyles(); @@ -42,11 +43,12 @@ function ClinicDetails() { const navigate = useNavigate(); const queryParams = new URLSearchParams(location.search); const [isLoading, setIsLoading] = useState(false); - const [companyData, setCompanyData] = useState(''); - const [companyAdminData, setCompanyAdminData] = useState(''); + const [clinicData, setClinicData] = useState(""); + const [clinicFiles, setClinicFiles] = useState(""); + const [clinicAdminData, setClinicAdminData] = useState(""); const [isShowReasonModel, setIsShowReasonModel] = useState(false); const [updateFiles, setUpdateFiles] = useState([]); - const [buttonClickStatus, setButtonClickStatus] = useState(''); + const [buttonClickStatus, setButtonClickStatus] = useState(""); const [isRejectButtonShow, setIsRejectedButtonShow] = useState(false); const [isOnHoldButtonShow, setIsOnHoldButtonShow] = useState(false); const [isAcceptButtonShow, setIsAcceptedButtonShow] = useState(false); @@ -57,13 +59,12 @@ function ClinicDetails() { try { setIsLoading(true); const response = await getClinicsById(id); - setCompanyData(response?.data?.data); - setCompanyAdminData( - response?.data?.data?.companyUsers?.find((user) => user.isAdmin) || null - ); + setClinicData(response?.data?.data?.clinic); + setClinicFiles(response?.data?.data?.clinic_files); + setClinicAdminData(response?.data?.data?.creator); } catch (error) { // eslint-disable-next-line no-console - console.error('Error fetching data:', error); + console.error("Error fetching data:", error); } finally { setIsLoading(false); } @@ -75,17 +76,17 @@ function ClinicDetails() { // ...................breadcrumbs array........................ const breadcrumbs = [ { - label: 'Dashboard', - path: '/', + label: "Dashboard", + path: "/", }, { - label: 'Clinic List', - path: '/clinics', - query: { tab: queryParams.get('tab') || 'UNREGISTERED' }, + label: "Clinic List", + path: "/clinics", + query: { tab: queryParams.get("tab") || "UNREGISTERED" }, }, { - label: 'Clinics Details', - path: '', + label: "Clinics Details", + path: "", }, ]; @@ -93,7 +94,7 @@ function ClinicDetails() { const updateCompany = async (body) => { try { - const response = await updateClinicStatus(companyData?.id, body); + const response = await updateClinicStatus(body); if (response?.data?.error) { pushNotification(response?.data?.message, NOTIFICATION.ERROR); setIsShowReasonModel(false); @@ -105,7 +106,7 @@ function ClinicDetails() { } } catch (error) { // eslint-disable-next-line no-console - console.error('Error', error); + console.error("Error", error); } }; @@ -116,7 +117,7 @@ function ClinicDetails() { setIsOnHoldButtonShow(false); }; const defaultFormData = useRef({ - reason: '', + reason: "", }); const fieldRefs = { @@ -124,7 +125,7 @@ function ClinicDetails() { }; const validationSchema = Yup.object({ - reason: Yup.string().required('Reason is required.'), + reason: Yup.string().required("Reason is required."), }); const formik = useFormik({ initialValues: defaultFormData.current, @@ -148,9 +149,9 @@ function ClinicDetails() { }; const getNotReviewedFileCount = () => { - if (companyData?.companyDocuments) { - const notReviewedDocuments = companyData.companyDocuments.filter( - (document) => document.status === 'NOT_REVIEWED' + if (clinicData?.companyDocuments) { + const notReviewedDocuments = clinicData.companyDocuments.filter( + (document) => document.status === "NOT_REVIEWED" ); return notReviewedDocuments.length; } @@ -160,19 +161,19 @@ function ClinicDetails() { const handleCancelRejection = async () => { try { setIsLoading(true); - // const resp = await companyCancelRejection(companyData?.id); - // if (resp?.data?.error) { - // pushNotification(resp?.data?.message, NOTIFICATION.ERROR); - // fetchData(); - // } else { - // pushNotification(resp?.data?.message, NOTIFICATION.SUCCESS); - // fetchData(); - // setIsOnHoldButtonShow(false); - // setIsRejectedButtonShow(false); - // setUpdateFiles(() => []); - // setButtonClickStatus(''); - // } - console.log("cancel rejection") + // const resp = await companyCancelRejection(clinicData?.id); + // if (resp?.data?.error) { + // pushNotification(resp?.data?.message, NOTIFICATION.ERROR); + // fetchData(); + // } else { + // pushNotification(resp?.data?.message, NOTIFICATION.SUCCESS); + // fetchData(); + // setIsOnHoldButtonShow(false); + // setIsRejectedButtonShow(false); + // setUpdateFiles(() => []); + // setButtonClickStatus(''); + // } + console.log("cancel rejection"); } catch (error) { // Handle error if needed } finally { @@ -192,17 +193,17 @@ function ClinicDetails() { }); const data = { - userId: companyAdminData?.id, - reason: values ? values.reason : '', + clinic_id: clinicData?.id, + rejection_reason: values ? values.reason : "", status: - buttonClickStatus === 'Rejected' - ? CLINIC_STATUS.REJECTED - : buttonClickStatus === 'On Hold' - ? CLINIC_STATUS.ON_HOLD - : CLINIC_STATUS.APPROVED, - documentStatus: { - ...documentStatusMap, - }, + buttonClickStatus === "Rejected" + ? CLINIC_STATUS.REJECTED.toLowerCase() + : buttonClickStatus === "On Hold" + ? CLINIC_STATUS.UNDER_REVIEW.toLowerCase() + : CLINIC_STATUS.ACTIVE.toLowerCase(), + // documentStatus: { + // ...documentStatusMap, + // }, }; return data; }; @@ -210,17 +211,17 @@ function ClinicDetails() { // .................handle accept reject and on hold event............. const handleRejectClick = () => { setIsShowReasonModel(true); - setButtonClickStatus('Rejected'); + setButtonClickStatus("Rejected"); }; const handleOnHoldClick = () => { setIsShowReasonModel(true); - setButtonClickStatus('On Hold'); + setButtonClickStatus("On Hold"); }; const handleAcceptClick = () => { // setIsShowReasonModel(true); - setButtonClickStatus('Accepted'); + setButtonClickStatus("Accepted"); const body = formatedData(); updateCompany(body); @@ -229,31 +230,28 @@ function ClinicDetails() { // ..................update file use effects................ useEffect(() => { setIsRejectedButtonShow( - updateFiles.some( - (file) => file.status === CLINIC_DOCUMENT_STATUS.REJECTED - ) + clinicFiles.abn_doc_is_verified != true && + clinicFiles.contract_doc_is_verified != true ); - setIsOnHoldButtonShow( - updateFiles.some( - (file) => file.status === CLINIC_DOCUMENT_STATUS.REJECTED - ) - ); - const notReviewedFileCount = getNotReviewedFileCount(); - const areAllFilesApproved = updateFiles.every( - (file) => file.status === CLINIC_DOCUMENT_STATUS.APPROVED + setIsOnHoldButtonShow( + clinicFiles.abn_doc_is_verified != true && + clinicFiles.contract_doc_is_verified != true ); + + const areAllFilesApproved = + clinicFiles.abn_doc_is_verified == true && + clinicFiles.contract_doc_is_verified == true; + if ( - notReviewedFileCount === updateFiles.length && areAllFilesApproved && - companyData?.status !== CLINIC_STATUS.ON_HOLD && - companyData?.status !== CLINIC_STATUS.REJECTED + clinicData?.status === CLINIC_STATUS.UNDER_REVIEW.toLowerCase() ) { setIsAcceptedButtonShow(true); } else { setIsAcceptedButtonShow(false); } - }, [updateFiles]); + }, [clinicFiles]); // ..............handle table change.......... const handleTabChange = (event, newValue) => { @@ -261,7 +259,7 @@ function ClinicDetails() { }; const handleEditCompanyInfo = () => { - navigate(`/clinics/${companyData?.id}/edit`); + navigate(`/clinics/${clinicData?.id}/edit`); }; const handleSubmitClick = async () => { @@ -275,10 +273,10 @@ function ClinicDetails() { if (firstErrorRef) { // Scroll to the first invalid field smoothly - if (typeof firstErrorRef?.scrollIntoView === 'function') { + if (typeof firstErrorRef?.scrollIntoView === "function") { firstErrorRef?.scrollIntoView({ - behavior: 'smooth', - block: 'center', + behavior: "smooth", + block: "center", }); } @@ -302,7 +300,7 @@ function ClinicDetails() { pageTitle="Clinics Details" hideAddButton extraComponent={ - companyData.status === CLINIC_STATUS.APPROVED ? ( + clinicData.status === CLINIC_STATUS.ACTIVE.toLowerCase() ? ( <> - {companyData?.status === CLINIC_STATUS.REJECTED && ( + {clinicData?.status === CLINIC_STATUS.REJECTED && ( - {/* {companyData.status === CLINIC_STATUS.APPROVED ? ( + {clinicData.status === CLINIC_STATUS.ACTIVE ? ( <> - {/* */} - {/* */} - {/* + + - - -
- */} - {/* ) : ( + + ) : ( - )} */} + )} )} + {isShowReasonModel && ( ( - {buttonClickStatus === 'Rejected' - ? 'Please define a reason for rejecting this company.' - : 'Please define a reason for putting this company on hold.'} + {buttonClickStatus === "Rejected" + ? "Please define a reason for rejecting this company." + : "Please define a reason for putting this company on hold."} - {buttonClickStatus === 'Rejected' - ? 'Reason for Rejection' - : 'Reason for On Hold'} + {buttonClickStatus === "Rejected" + ? "Reason for Rejection" + : "Reason for On Hold"} diff --git a/src/views/ClinicsList/index.jsx b/src/views/ClinicsList/index.jsx index 2f7dfe3..d879b91 100644 --- a/src/views/ClinicsList/index.jsx +++ b/src/views/ClinicsList/index.jsx @@ -11,7 +11,7 @@ import FileDownloadIcon from '@mui/icons-material/FileDownload'; import { useStyles } from "./clinicListStyles"; -import { CLINIC_STATUS, CLINIC_TYPE, PLAN_STATUS_TYPE } from "../../constants"; +import { CLINIC_STATUS, CLINIC_TYPE, NOT_AVAILABLE_TEXT, PLAN_STATUS_TYPE } from "../../constants"; import { getClinics } from "../../services/clinics.service"; import CustomBreadcrumbs from "../../components/CustomBreadcrumbs"; import Table from "../../components/Table"; @@ -115,14 +115,14 @@ const ClinicsList = () => { enableColumnFilter: false, enableSorting: true, size: 100, - accessorKey: "updatedAt", + accessorKey: "update_time", header: "Req.Raised On", Cell: ({ row }) => ( <>
- {row?.original?.requestRaisedOn + {row?.original?.update_time ? format( - new Date(row.original.requestRaisedOn), + new Date(row.original.update_time), "dd MMM yyyy" ) : NOT_AVAILABLE_TEXT} @@ -196,8 +196,8 @@ const ClinicsList = () => { { Document Resubmitted
- ) : status === CLINIC_STATUS.REJECTED ? ( + ) : status === CLINIC_STATUS.REJECTED.toLowerCase() ? ( "Rejected" ) : ( "" @@ -216,20 +216,20 @@ const ClinicsList = () => { } style={{ backgroundColor: - status === CLINIC_STATUS.ON_HOLD + status === CLINIC_STATUS.ON_HOLD.toLowerCase() ? theme.palette.orange.light - : status === CLINIC_STATUS.NOT_REVIEWED || - status === CLINIC_STATUS.REJECTED + : status === CLINIC_STATUS.NOT_REVIEWED.toLowerCase() || + status === CLINIC_STATUS.REJECTED.toLowerCase() ? theme.palette.primary.highlight : status === CLINIC_STATUS.APPROVAL_PENDING_DOCUMENT_RESUBMITTED ? theme.palette.primary.highlight : theme.palette.blue.light, color: - status === CLINIC_STATUS.ON_HOLD + status === CLINIC_STATUS.ON_HOLD.toLowerCase() ? theme.palette.orange.main - : status === CLINIC_STATUS.NOT_REVIEWED || - status === CLINIC_STATUS.REJECTED + : status === CLINIC_STATUS.NOT_REVIEWED.toLowerCase() || + status === CLINIC_STATUS.REJECTED.toLowerCase() ? theme.palette.primary.main : status === CLINIC_STATUS.APPROVAL_PENDING_DOCUMENT_RESUBMITTED @@ -406,9 +406,10 @@ const ClinicsList = () => { type, }; const resp = await getClinics(params); + console.log(resp) return { - data: resp?.data?.data?.records, - rowCount: resp?.data?.data?.totalCount, + data: resp?.data?.data?.data?.clinics, + rowCount: resp?.data?.data?.total, }; }; @@ -444,7 +445,8 @@ const ClinicsList = () => { }, { label: "Clinic List", - path: "", + path: "/clinics", + query: { tab: queryParams.get('tab') || 'UNREGISTERED' } }, ]; diff --git a/src/views/Dashboard/Tiles/SuperAdminTotals.jsx b/src/views/Dashboard/Tiles/SuperAdminTotals.jsx index a5709eb..0874ee6 100644 --- a/src/views/Dashboard/Tiles/SuperAdminTotals.jsx +++ b/src/views/Dashboard/Tiles/SuperAdminTotals.jsx @@ -42,7 +42,7 @@ const SuperAdminTotals = ({ isLoading, data }) => { heading={`Approved`} isLoading={isLoading} viewAllClick={() => viewAllClick(false)} - value={rejected} + value={registered} helperText={"Clinics"} color={theme.palette.grey[52]} /> @@ -52,7 +52,7 @@ const SuperAdminTotals = ({ isLoading, data }) => { heading={`Rejected`} isLoading={isLoading} viewAllClick={() => viewAllClick(false)} - value={registered} + value={rejected} helperText={"Clinics"} color={theme.palette.grey[57]} /> diff --git a/src/views/Dashboard/Tiles/Totals.jsx b/src/views/Dashboard/Tiles/Totals.jsx index 14d27dd..58f646e 100644 --- a/src/views/Dashboard/Tiles/Totals.jsx +++ b/src/views/Dashboard/Tiles/Totals.jsx @@ -5,6 +5,7 @@ import ProtectedComponent from '../../../components/ProtectedComponent'; import { getClinicsDashboardStatsById } from '../../../services/clinics.service'; import TotalNumber from '../components/TotalNumber'; import { useStyles } from '../dashboardStyles'; +import { getDashboardStats } from '../../../services/dashboard.services'; const Totals = ({ data, setData }) => { const classes = useStyles(); @@ -19,12 +20,11 @@ const Totals = ({ data, setData }) => { const fetchData = async () => { try { setIsLoading(true); - const response = {}; - // const response = await getClinicsDashboardStatsById(); + const response = await getDashboardStats(); const apiData = response?.data?.data || {}; setData({ - activeJobs: apiData.recruitmentAnalytics?.activeJobs, - totalMaxJobPostings: apiData.totalMaxJobPostings, + activeJobs: apiData?.active, + totalMaxJobPostings: apiData.inactive, }); } catch (error) { console.error('Error fetching data:', error); diff --git a/src/views/Dashboard/components/PaymentConfig.jsx b/src/views/Dashboard/components/PaymentConfig.jsx new file mode 100644 index 0000000..ed78ef2 --- /dev/null +++ b/src/views/Dashboard/components/PaymentConfig.jsx @@ -0,0 +1,205 @@ +import React, { useState, useEffect } from "react"; +import { + Box, + Typography, + TextField, + Button, + Paper, + Grid, + Snackbar, + Alert, + InputAdornment +} from "@mui/material"; +import { useStyles } from "../dashboardStyles"; +import { getSignupMasterPricing, setSignupMasterPricing } from "../../../services/dashboard.services"; + +const PaymentConfig = () => { + const classes = useStyles(); + + // Initial payment configuration state + const [paymentConfig, setPaymentConfig] = useState({ + setup_fees: "0.00", + subscription_fees: "0.00", + per_call_charges: "0.00" + }); + + const fetchPaymentConfig = async () => { + try { + const response = await getSignupMasterPricing(); + const apiData = response?.data?.data || {}; + setPaymentConfig({ + setup_fees: apiData?.setup_fees, + subscription_fees: apiData?.subscription_fees, + per_call_charges: apiData?.per_call_charges + }); + } catch (error) { + console.error("Error fetching payment configuration:", error); + } + }; + + useEffect(() => { + fetchPaymentConfig(); + }, []); + + // State for tracking if form has been edited + const [isEdited, setIsEdited] = useState(false); + + // State for notification + const [notification, setNotification] = useState({ + open: false, + message: "", + severity: "success" + }); + + // Handle input change + const handleInputChange = (e) => { + const { name, value } = e.target; + + // Allow empty values, numbers, and decimal points with up to 2 decimal places + // This regex allows for more flexible input including empty string and partial numbers + if (value !== "" && !/^\d*(\.\d{0,2})?$/.test(value)) { + return; + } + + setPaymentConfig({ + ...paymentConfig, + [name]: value + }); + + setIsEdited(true); + }; + + // Handle save configuration + const handleSaveConfig = async () => { + try { + const response = await setSignupMasterPricing(paymentConfig); + const apiData = response?.data?.data || {}; + setPaymentConfig({ + setup_fees: apiData?.setup_fees, + subscription_fees: apiData?.subscription_fees, + per_call_charges: apiData?.per_call_charges + }); + } catch (error) { + console.error("Error fetching payment configuration:", error); + } + + setNotification({ + open: true, + message: "Payment configuration saved successfully", + severity: "success" + }); + + setIsEdited(false); + + // In a real implementation, you would save this data to your backend + console.log("Saving payment configuration:", paymentConfig); + }; + + // Handle notification close + const handleCloseNotification = () => { + setNotification({ + ...notification, + open: false + }); + }; + + return ( + + + + Payment Configuration + + + Configure payment details for new clinic registrations + + + + + + $, + }} + variant="outlined" + /> + + + + $, + }} + variant="outlined" + /> + + + + $, + endAdornment: /call, + }} + variant="outlined" + /> + + + + + + + + {/* Notification */} + + + {notification.message} + + + + ); +}; + +export default PaymentConfig; diff --git a/src/views/Dashboard/components/SuperAdmin.jsx b/src/views/Dashboard/components/SuperAdmin.jsx index 904a7e4..644cc3f 100644 --- a/src/views/Dashboard/components/SuperAdmin.jsx +++ b/src/views/Dashboard/components/SuperAdmin.jsx @@ -3,6 +3,8 @@ import { Box } from "@mui/system"; import React from "react"; import { useStyles } from "../dashboardStyles"; import SuperAdminTotals from "../Tiles/SuperAdminTotals"; +import PaymentConfig from "./PaymentConfig"; +import { getDashboardStats } from "../../../services/dashboard.services"; const SuperAdmin = () => { const classes = useStyles(); @@ -17,22 +19,14 @@ const SuperAdmin = () => { const fetchDashboardStats = async () => { try { setIsLoading(true); - // const response = await getAdminDashboardStats(); - // const apiData = response?.data?.data || {}; - // setData({ - // totalAccounts: apiData?.dashboardStatistics?.totalAccounts, - // registrationRequest: apiData?.dashboardStatistics?.registrationRequest, - // rejected: apiData?.dashboardStatistics?.rejected, - // registered: apiData?.dashboardStatistics?.registered, - // }); - setTimeout(() => { + const response = await getDashboardStats(); + const apiData = response?.data?.data || {}; setData({ - totalAccounts: 10, - registrationRequest: 5, - rejected: 2, - registered: 8, + totalAccounts: apiData?.totalClinics, + registrationRequest: apiData?.totalUnderReviewClinics, + rejected: apiData?.totalRejectedClinics, + registered: apiData?.totalActiveClinics, }); - }, 1000); } catch (error) { console.error("Error fetching data:", error); } finally { @@ -53,6 +47,11 @@ const SuperAdmin = () => {
+ + {/* Payment Configuration Section */} + + + ); }; diff --git a/src/views/Dashboard/index.jsx b/src/views/Dashboard/index.jsx index eec9a36..fc24aca 100644 --- a/src/views/Dashboard/index.jsx +++ b/src/views/Dashboard/index.jsx @@ -2,15 +2,12 @@ import React, { useEffect, useState } from "react"; import { useSelector, useDispatch } from "react-redux"; import Loader from "../components/Loader"; import ThankYou from "../ThankYou/"; -import { Box, Typography } from '@mui/material'; +import { Box, Typography } from "@mui/material"; import SuperAdmin from "./components/SuperAdmin"; -import { - selectUserRole, - setUserRole, - USER_ROLES, -} from "../../redux/userRoleSlice"; +import { selectUserRole, setUserRole } from "../../redux/userRoleSlice"; import Totals from "./Tiles/Totals"; -import { useStyles } from './dashboardStyles'; +import { useStyles } from "./dashboardStyles"; +import { USER_ROLES } from "../../constants"; function Dashboard() { const classes = useStyles(); @@ -36,12 +33,10 @@ function Dashboard() { useEffect(() => { // Determine user role based on user data from login reducer if (user) { - // Check if user is a super admin // This logic can be adjusted based on your specific role criteria - const isSuperAdmin = user.isBsAdmin || - (user.isAdmin && user.permissions?.includes("SUPER_ADMIN_PERMISSION")); - // Set the appropriate role in Redux + const isSuperAdmin = + user.userType == USER_ROLES.SUPER_ADMIN.toLowerCase(); if (isSuperAdmin) { dispatch(setUserRole(USER_ROLES.SUPER_ADMIN)); } else { @@ -61,41 +56,15 @@ function Dashboard() { {/* */} - - - - Dashboard - - - + + + + Dashboard + - {/* - - setPostAJobModal(true)} - isJobPostAvailable={data?.remainingJobPosts > 0} - /> - - {hasSubscriptionManagementPermission && ( - - - - )} - {postAJobModal && ( - setPostAJobModal(false)} - > - )} - */} + + ); diff --git a/src/views/Login/LoginForm.jsx b/src/views/Login/LoginForm.jsx index 762f45f..f06059e 100644 --- a/src/views/Login/LoginForm.jsx +++ b/src/views/Login/LoginForm.jsx @@ -14,7 +14,7 @@ import React, { useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import * as Yup from 'yup'; -import { messaging } from '../../config/firebase'; +import { initializeMessaging } from '../../config/firebase'; import { NOTIFICATION } from '../../constants'; import { pushNotification } from '../../utils/notification'; import { @@ -64,9 +64,6 @@ function LoginForm() { // Show success notification pushNotification('Login successful', NOTIFICATION.SUCCESS); - - formik.setSubmitting(false); - // Navigate to dashboard immediately without waiting for profile navigate('/'); } catch (error) { diff --git a/src/views/Login/loginAction.js b/src/views/Login/loginAction.js index 8eda59c..a09e902 100644 --- a/src/views/Login/loginAction.js +++ b/src/views/Login/loginAction.js @@ -1,106 +1,106 @@ import { axiosInstance } from '../../config/api'; import { LOGIN, PROFILE } from './loginActionTypes'; -// export const login = (data) => ({ -// type: LOGIN, -// payload: axiosInstance.post('/auth/companies/login', data), -// }); +export const login = (data) => ({ + type: LOGIN, + payload: axiosInstance.post('/auth/login', data), +}); -// export const profile = () => ({ -// type: PROFILE, -// payload: axiosInstance.get('/me'), -// }); +export const profile = () => ({ + type: PROFILE, + payload: axiosInstance.get('/users/me'), +}); -export const login = (data) => { - // Determine user type based on email - const isSuperAdmin = data.email === 'admin@gmail.com'; - const userRole = isSuperAdmin ? 'SUPER_ADMIN' : 'CLINIC_ADMIN'; - const userId = isSuperAdmin ? 'super-admin-123' : 'clinic-admin-123'; +// export const login = (data) => { +// // Determine user type based on email +// const isSuperAdmin = data.email === 'admin@gmail.com'; +// const userRole = isSuperAdmin ? 'SUPER_ADMIN' : 'CLINIC_ADMIN'; +// const userId = isSuperAdmin ? 'super-admin-123' : 'clinic-admin-123'; - // Store token and user type in localStorage to simulate a real login - localStorage.setItem('token', `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`); - localStorage.setItem('userType', userRole); +// // Store token and user type in localStorage to simulate a real login +// localStorage.setItem('token', `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`); +// localStorage.setItem('userType', userRole); - // Mock successful login response - const mockLoginResponse = { - data: { - message: 'Login successful', - token: `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`, - user: { - id: userId, - email: data.email, - role: userRole - } - } - }; +// // Mock successful login response +// const mockLoginResponse = { +// data: { +// message: 'Login successful', +// token: `mock-jwt-token-for-${isSuperAdmin ? 'super' : 'clinic'}-admin`, +// user: { +// id: userId, +// email: data.email, +// role: userRole +// } +// } +// }; - return { - type: LOGIN, - // Use a real Promise with a slight delay to simulate network request - payload: new Promise(resolve => setTimeout(() => resolve(mockLoginResponse), 300)) - }; -}; +// return { +// type: LOGIN, +// // Use a real Promise with a slight delay to simulate network request +// payload: new Promise(resolve => setTimeout(() => resolve(mockLoginResponse), 300)) +// }; +// }; -export const profile = () => { - // Get user type from localStorage to determine which profile to return - const userType = localStorage.getItem('userType') || 'CLINIC_ADMIN'; - const isSuperAdmin = userType === 'SUPER_ADMIN'; +// export const profile = () => { +// // Get user type from localStorage to determine which profile to return +// const userType = localStorage.getItem('userType') || 'CLINIC_ADMIN'; +// const isSuperAdmin = userType === 'SUPER_ADMIN'; - // Create appropriate mock profile based on user type - let profileData; +// // Create appropriate mock profile based on user type +// let profileData; - if (isSuperAdmin) { - // Super Admin profile - profileData = { - id: 'super-admin-123', - name: 'Super Administrator', - email: 'admin@gmail.com', - role: 'SUPER_ADMIN', - isAdmin: true, - isBsAdmin: true, // This flag identifies super admin - permissions: [ - 'CREATE_REC_USERS', - 'READ_REC_USERS', - 'DELETE_REC_USERS', - 'SUPER_ADMIN_PERMISSION' - ] - }; - } else { - // Clinic Admin profile - profileData = { - id: 'clinic-admin-123', - name: 'Clinic Administrator', - email: 'admin@clinic.com', - role: 'CLINIC_ADMIN', - isAdmin: true, - isBsAdmin: false, - permissions: [ - 'CREATE_REC_USERS', - 'READ_REC_USERS', - 'DELETE_REC_USERS' - ], - clinic: { - id: 'clinic-123', - name: 'Health Plus Clinic', - address: '123 Medical Center Blvd', - city: 'Healthcare City', - state: 'HC', - zipCode: '12345', - phone: '555-123-4567' - } - }; - } +// if (isSuperAdmin) { +// // Super Admin profile +// profileData = { +// id: 'super-admin-123', +// name: 'Super Administrator', +// email: 'admin@gmail.com', +// role: 'SUPER_ADMIN', +// isAdmin: true, +// isSuperAdmin: true, // This flag identifies super admin +// permissions: [ +// 'CREATE_REC_USERS', +// 'READ_REC_USERS', +// 'DELETE_REC_USERS', +// 'SUPER_ADMIN_PERMISSION' +// ] +// }; +// } else { +// // Clinic Admin profile +// profileData = { +// id: 'clinic-admin-123', +// name: 'Clinic Administrator', +// email: 'admin@clinic.com', +// role: 'CLINIC_ADMIN', +// isAdmin: true, +// isSuperAdmin: false, +// permissions: [ +// 'CREATE_REC_USERS', +// 'READ_REC_USERS', +// 'DELETE_REC_USERS' +// ], +// clinic: { +// id: 'clinic-123', +// name: 'Health Plus Clinic', +// address: '123 Medical Center Blvd', +// city: 'Healthcare City', +// state: 'HC', +// zipCode: '12345', +// phone: '555-123-4567' +// } +// }; +// } - // Create the response with the nested data structure the reducer expects - const mockProfileResponse = { - data: { - data: profileData - } - }; +// // Create the response with the nested data structure the reducer expects +// const mockProfileResponse = { +// data: { +// data: profileData +// } +// }; - return { - type: PROFILE, - // Use a real Promise with a slight delay to simulate network request - payload: new Promise(resolve => setTimeout(() => resolve(mockProfileResponse), 300)) - }; -}; +// return { +// type: PROFILE, +// // Use a real Promise with a slight delay to simulate network request +// payload: new Promise(resolve => setTimeout(() => resolve(mockProfileResponse), 300)) +// }; +// }; diff --git a/src/views/Login/loginReducer.js b/src/views/Login/loginReducer.js index 50043bf..dc29ed3 100644 --- a/src/views/Login/loginReducer.js +++ b/src/views/Login/loginReducer.js @@ -9,11 +9,13 @@ import { } from './loginActionTypes'; const initialState = {}; + const loginPending = (state) => ({ ...state, }); -const loginFulfilled = (state) => ({ +const loginFulfilled = (state, payload) => ({ ...state, + token: payload?.payload?.data?.data, }); const loginRejected = (state) => ({ ...state, @@ -39,7 +41,7 @@ const profileFulfilled = (state, payload) => { ); }); // Check if user is admin, add additional permissions - if (user.isAdmin && !user?.isBsAdmin) { + if (user.isAdmin && !user?.isSuperAdmin) { userPermissions.push( 'CREATE_REC_USERS', 'READ_REC_USERS', diff --git a/src/views/MasterData/index.jsx b/src/views/MasterData/index.jsx index 3b470be..cf3cd23 100644 --- a/src/views/MasterData/index.jsx +++ b/src/views/MasterData/index.jsx @@ -1,9 +1,9 @@ -import AddIcon from '@mui/icons-material/Add'; -import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; -import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; -import CloseIcon from '@mui/icons-material/Close'; -import PersonAddIcon from '@mui/icons-material/PersonAdd'; -import SearchIcon from '@mui/icons-material/Search'; +import AddIcon from "@mui/icons-material/Add"; +import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; +import CloseIcon from "@mui/icons-material/Close"; +import PersonAddIcon from "@mui/icons-material/PersonAdd"; +import SearchIcon from "@mui/icons-material/Search"; import { Alert, Box, @@ -26,329 +26,328 @@ import { TableRow, TextField, Typography, -} from '@mui/material'; -import React, { useState } from 'react'; -import CustomBreadcrumbs from '../../components/CustomBreadcrumbs'; -import PageHeader from '../../components/PageHeader'; +} from "@mui/material"; +import React, { useState } from "react"; +import CustomBreadcrumbs from "../../components/CustomBreadcrumbs"; +import PageHeader from "../../components/PageHeader"; const MasterDataManagement = () => { - // State for form fields - const [appointmentType, setAppointmentType] = useState(''); - const queryParams = new URLSearchParams(location.search); - - // State for staff list - const [staffList, setStaffList] = useState([]); - - // State for dialog - const [openDialog, setOpenDialog] = useState(false); - - // State for search - const [searchQuery, setSearchQuery] = useState(''); - - // State for pagination - const [page, setPage] = useState(1); - const rowsPerPage = 10; - - // State for notification - const [notification, setNotification] = useState({ - open: false, - message: '', - severity: 'success', - }); - - // Handle dialog open/close - const handleOpenDialog = () => { - setOpenDialog(true); - }; - - const handleCloseDialog = () => { - setOpenDialog(false); - // Clear form - setAppointmentType(''); - }; - - // Handle form submission - const handleSubmit = (e) => { - e.preventDefault(); - - // Add new staff member - const newStaff = { - id: staffList.length + 1, - appointmentType, - }; - - setStaffList([...staffList, newStaff]); - - // Close dialog - handleCloseDialog(); - - // Show success notification - setNotification({ - open: true, - message: 'Staff member added successfully!', - severity: 'success', - }); - }; - - // Handle notification close - const handleCloseNotification = () => { - setNotification({ - ...notification, - open: false, - }); - }; - - // ...................breadcrumbs array........................ - const breadcrumbs = [ - { - label: 'Dashboard', - path: '/', - }, - { - label: 'Master Data Management', - path: '/masterData', - }, - ]; - - return ( - + // State for form fields + const [appointmentType, setAppointmentType] = useState(""); + const queryParams = new URLSearchParams(location.search); - - - - - - {/* Staff List Header with Add Button */} - { + setOpenDialog(true); + }; + + const handleCloseDialog = () => { + setOpenDialog(false); + // Clear form + setAppointmentType(""); + }; + + // Handle form submission + const handleSubmit = (e) => { + e.preventDefault(); + + // Add new staff member + const newStaff = { + id: staffList.length + 1, + appointmentType, + }; + + setStaffList([...staffList, newStaff]); + + // Close dialog + handleCloseDialog(); + + // Show success notification + setNotification({ + open: true, + message: "Staff member added successfully!", + severity: "success", + }); + }; + + // Handle notification close + const handleCloseNotification = () => { + setNotification({ + ...notification, + open: false, + }); + }; + + // ...................breadcrumbs array........................ + const breadcrumbs = [ + { + label: "Dashboard", + path: "/", + }, + { + label: "Master Data Management", + path: "/masterData", + }, + ]; + + return ( + + + + + + + {/* Staff List Header with Add Button */} + + + Master Appointment Type List + + + + + + {/* Search Box */} + + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + sx={{ + backgroundColor: "#fff", + "& .MuiOutlinedInput-root": { + borderRadius: 2, + }, + }} + /> + + + {/* Staff List Table */} + + + + + Sr. No. + + Appointment Type + + + + + {staffList.length > 0 ? ( + staffList + .filter((staff) => + `${staff.appointmentType}` + .toLowerCase() + .includes(searchQuery.toLowerCase()) + ) + .slice((page - 1) * rowsPerPage, page * rowsPerPage) + .map((staff, index) => ( + + + {(page - 1) * rowsPerPage + index + 1} + + {staff.appointmentType} + + )) + ) : ( + + + No Appointment Type added yet + + + )} + +
+
+ + {/* Pagination */} + {staffList.length > 0 && ( + + + + + - - {/* Search Box */} - + )} +
+ + + + {/* Add Staff Dialog */} + + + + + + Add New Appointment Type + + + + + + + + + + + setSearchQuery(e.target.value)} - InputProps={{ - startAdornment: ( - - - - ), - }} - sx={{ - backgroundColor: '#fff', - '& .MuiOutlinedInput-root': { - borderRadius: 2, - }, + margin="normal" + value={appointmentType} + onChange={(e) => setAppointmentType(e.target.value)} + placeholder="Appointment Type" + required + InputLabelProps={{ + shrink: true, }} /> - - {/* Staff List Table */} - - - - - Sr. No. - Appointment Type - - - - {staffList.length > 0 ? ( - staffList - .filter( - (staff) => - `${staff.appointmentType}` - .toLowerCase() - .includes(searchQuery.toLowerCase()) - ) - .slice((page - 1) * rowsPerPage, page * rowsPerPage) - .map((staff, index) => ( - - - {(page - 1) * rowsPerPage + index + 1} - - {staff.appointmentType} - - )) - ) : ( - - - No Appointment Type added yet - - - )} - -
-
- - {/* Pagination */} - {staffList.length > 0 && ( - - - - - - - - )} -
- - {/* Add Staff Dialog */} - - + + + - - - - {/* Notification */} - + + + + {/* Notification */} + + - - {notification.message} - - -
- ); + {notification.message} + + + + ); }; export default MasterDataManagement; diff --git a/src/views/MockPayment/index.jsx b/src/views/MockPayment/index.jsx index 46703ac..4608e26 100644 --- a/src/views/MockPayment/index.jsx +++ b/src/views/MockPayment/index.jsx @@ -227,59 +227,7 @@ const PaymentPage = () => {
} /> - - {paymentMethod === 'card' && ( - - - setCardNumber(formatCardNumber(e.target.value)) - } - placeholder="1234 5678 9012 3456" - inputProps={{ maxLength: 19 }} - /> - - - - - setCardExpiry(formatExpiry(e.target.value)) - } - placeholder="MM/YY" - inputProps={{ maxLength: 5 }} - /> - - - setCardCVC(e.target.value)} - placeholder="123" - inputProps={{ maxLength: 3 }} - /> - - - - setName(e.target.value)} - placeholder="John Smith" - /> - - )} - + {paymentMethod === 'net_banking' && ( { return; } - if (!messaging) return; - try { - const currentToken = await getToken(messaging, { + // Initialize messaging if not already initialized + const messagingInstance = await initializeMessaging(); + if (!messagingInstance) { + console.log('Firebase messaging is not supported in this browser'); + return; + } + + const currentToken = await getToken(messagingInstance, { vapidKey: FB_VAPID_KEY, }); + if (currentToken) { await saveFcmToken({ token: currentToken, diff --git a/src/views/PaymentManagement/index.jsx b/src/views/PaymentManagement/index.jsx new file mode 100644 index 0000000..61a6405 --- /dev/null +++ b/src/views/PaymentManagement/index.jsx @@ -0,0 +1,330 @@ +import AddIcon from "@mui/icons-material/Add"; +import CloseIcon from "@mui/icons-material/Close"; +import { + Box, + Button, + Checkbox, + Container, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControlLabel, + IconButton, + MenuItem, + Paper, + Snackbar, + Alert, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, +} from "@mui/material"; +import React, { useState } from "react"; +import CustomBreadcrumbs from "../../components/CustomBreadcrumbs"; +import PageHeader from "../../components/PageHeader"; + +const PaymentManagement = () => { + // State for payment dialog + const [paymentDialogOpen, setPaymentDialogOpen] = useState(false); + const [paymentData, setPaymentData] = useState({ + clinicName: "", + setupFeeWaived: false, + specialOffer: false, + configurationMonth: 3 + }); + + // State for payments list + const [paymentsList, setPaymentsList] = useState([]); + const [editPaymentIndex, setEditPaymentIndex] = useState(null); + + // State for notification + const [notification, setNotification] = useState({ + open: false, + message: "", + severity: "success", + }); + + // Handle payment dialog submission + const handleAddPayment = () => { + // Validate input + if (!paymentData.clinicName.trim()) { + setNotification({ + open: true, + message: "Please enter a clinic name", + severity: "error", + }); + return; + } + + if (editPaymentIndex !== null) { + // Update existing payment + const updatedPayments = [...paymentsList]; + updatedPayments[editPaymentIndex] = {...paymentData}; + setPaymentsList(updatedPayments); + + setNotification({ + open: true, + message: "Payment configuration updated successfully", + severity: "success", + }); + } else { + // Add new payment + setPaymentsList([...paymentsList, {...paymentData}]); + + setNotification({ + open: true, + message: "Payment configuration added successfully", + severity: "success", + }); + } + + // Reset form and close dialog + setPaymentData({ + clinicName: "", + setupFeeWaived: false, + specialOffer: false, + configurationMonth: 3 + }); + setEditPaymentIndex(null); + setPaymentDialogOpen(false); + }; + + // Handle edit payment + const handleEditPayment = (index) => { + setEditPaymentIndex(index); + setPaymentData({...paymentsList[index]}); + setPaymentDialogOpen(true); + }; + + // Handle delete payment + const handleDeletePayment = (index) => { + const updatedPayments = [...paymentsList]; + updatedPayments.splice(index, 1); + setPaymentsList(updatedPayments); + + setNotification({ + open: true, + message: "Payment configuration deleted successfully", + severity: "success", + }); + }; + + // Handle notification close + const handleCloseNotification = () => { + setNotification({ + ...notification, + open: false, + }); + }; + + // Breadcrumbs + const breadcrumbs = [ + { label: "Dashboard", link: "/" }, + { label: "Payment Management", link: "/payment-management" }, + ]; + + return ( + + + + + + + {/* Payment Management Header with Add Button */} + + + Payment Configurations + + + + + + {/* Payment List Table */} + + + + + Clinic Name + Setup Fee Waived + Special Offer + Configuration Month + Actions + + + + {paymentsList.length > 0 ? ( + paymentsList.map((payment, index) => ( + + {payment.clinicName} + {payment.setupFeeWaived ? "Yes" : "No"} + {payment.specialOffer ? "Yes" : "No"} + + {payment.specialOffer ? payment.configurationMonth : "-"} + + + + + + + )) + ) : ( + + + No payment configurations found + + + )} + +
+
+
+ + {/* Payment Dialog */} + setPaymentDialogOpen(false)} + maxWidth="sm" + fullWidth + > + + {editPaymentIndex !== null ? "Edit Payment" : "Add New Payment"} + setPaymentDialogOpen(false)} + sx={{ + position: 'absolute', + right: 8, + top: 8, + color: (theme) => theme.palette.grey[500], + }} + > + + + + + + setPaymentData({...paymentData, clinicName: e.target.value})} + sx={{ mb: 2 }} + /> + + setPaymentData({...paymentData, setupFeeWaived: e.target.checked})} + /> + } + label="Setup fee waived" + sx={{ mb: 1 }} + /> + + setPaymentData({...paymentData, specialOffer: e.target.checked})} + /> + } + label="Special Offer: First 3 months free" + sx={{ mb: 1 }} + /> + + {paymentData.specialOffer && ( + setPaymentData({...paymentData, configurationMonth: e.target.value})} + sx={{ mt: 1 }} + > + {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((month) => ( + + {month} + + ))} + + )} + + + + + + + + + {/* Notification Snackbar */} + + + {notification.message} + + +
+ ); +}; + +export default PaymentManagement; \ No newline at end of file diff --git a/src/views/Signup/YourDetailsForm.jsx b/src/views/Signup/YourDetailsForm.jsx index 1d4f127..04f436a 100644 --- a/src/views/Signup/YourDetailsForm.jsx +++ b/src/views/Signup/YourDetailsForm.jsx @@ -18,6 +18,7 @@ import { Select, TextField, Toolbar, + Tooltip, Typography, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; @@ -54,7 +55,11 @@ import { ONLY_ALPHA_NUMERIC_ACCEPT_REGEX, WEBSITE_REGEX, } from "../../constants"; -import { fileUpload } from "../../services/file.upload.services"; +import { + fileUpload, + getPresignedUrl, + uploadToS3, +} from "../../services/file.upload.services"; import { pushNotification } from "../../utils/notification"; import { passwordLengthRegex, @@ -64,30 +69,44 @@ import { } from "../../utils/regex"; import { capitalizeAllLetters, capitalizeFirstLetter } from "../../utils/share"; import PasswordValidation from "../Login/component/PasswordValidation"; -import { updateFormDetails } from "./signupAction"; +import { resetFormData, updateFormDetails } from "./signupAction"; import { useStyles } from "./styles/signupStyles"; +import { getLatestClinicId } from "../../services/clinics.service"; +import { signup } from "../../services/auth.services"; +import { decodeJWT } from "../../services/jwt.services"; function YourDetailsForm() { const classes = useStyles(); const dispatch = useDispatch(); const navigate = useNavigate(); const theme = useTheme(); + const [latestClinicID, setLatestClinicID] = useState(0); const [otpField, setOtpField] = useState(false); const [isLoading, setIsLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [billingUsers, setBillingUsers] = useState([]); const [drawerOpen, setDrawerOpen] = useState(false); const [localityOption, setLocalityOption] = useState([]); const [countryOption, setCountryOption] = useState(["Australia", "India"]); const [pincodeData, setPincodeData] = useState(); const selectedLocalityRef = useRef(); + const [testConnection, setTestConnDone] = useState(false); + + useEffect(() => { + setIsLoading(true); + getLatestClinicId().then((res) => { + setLatestClinicID(res?.data?.data); + }); + setIsLoading(false); + }, []); // Form data from Redux const yourDetailsFormData = useSelector((state) => state.signup); // Logo state variables const logoRef = useRef(null); + const testConnectionRef = useRef(null); + const otpButtonRef = useRef(null); const fileInputRef = useRef(null); const [aspect, setAspect] = useState(1); const [logoname, setLogoname] = useState(); @@ -109,7 +128,10 @@ function YourDetailsForm() { { id: "91", name: "+91" }, ]; - const integrationOptions = ["BP Software", "Medical Director"]; + const integrationOptions = [ + { id: "bp", name: "BP Software" }, + { id: "medical_director", name: "Medical Director" }, + ]; // Default form data const defaultFormData = useRef({ @@ -120,26 +142,25 @@ function YourDetailsForm() { mobileNumber: "", mobilePrefix: "", confirmPassword: "", - currentEmail: "", otp: "", // Clinic details companyName: "", designation: "", + businessPhonePrefix: "", businessPhone: "", emergencyBusinessPhone: "", emergencyBusinessPhonePrefix: "", - businessPhonePrefix: "", businessFax: "", - companyLogo: "", - companyIndustry: "", + clinicLogo: "", + businessEmail: "", pincode: "", state: "", locality: "", country: "", fullAddress: "", - companyPANImage: "", - companyPANImage: "", + companyABNImageNumber: "", + companyABNImage: "", termsAccepted: "", practiceManagementSystem: "", practiceId: "", @@ -167,18 +188,18 @@ function YourDetailsForm() { emergencyBusinessPhone: useRef(null), emergencyBusinessPhonePrefix: useRef(null), businessFax: useRef(null), + businessEmail: useRef(null), practiceManagementSystem: useRef(null), practiceId: useRef(null), practiceName: useRef(null), designation: useRef(null), - companyIndustry: useRef(null), pincode: useRef(null), state: useRef(null), locality: useRef(null), country: useRef(null), fullAddress: useRef(null), - companyPANImage: useRef(null), - companyTANNumber: useRef(null), + companyABNImageNumber: useRef(null), + companyABNImage: useRef(null), // contract management contract: useRef(null), @@ -186,14 +207,10 @@ function YourDetailsForm() { if (yourDetailsFormData) { defaultFormData.current = { ...yourDetailsFormData }; + // setLocalityOption([yourDetailsFormData?.locality]) + selectedLocalityRef.current = yourDetailsFormData?.locality; } - useEffect(() => { - if (yourDetailsFormData) { - setBillingUsers(yourDetailsFormData?.billingUsers); - } - }, [yourDetailsFormData]); - const validationSchema = Yup.object().shape({ // Personal details validation name: Yup.string().required("Name is required"), @@ -206,6 +223,7 @@ function YourDetailsForm() { .matches(/^[0-9]+$/, "Mobile Number must be a valid number") .min(10, "Mobile number should have 10 digits only.") .max(10, "Mobile number should have 10 digits only."), + mobilePrefix: Yup.string().required("Mobile Prefix is required"), password: Yup.string() .matches(passwordLengthRegex, "Password must be at least 8 characters") .matches(passwordLetterRegex, "Password must contain at least one letter") @@ -218,7 +236,6 @@ function YourDetailsForm() { confirmPassword: Yup.string() .oneOf([Yup.ref("password"), null], "Passwords must match") .required("The password must match"), - currentEmail: Yup.string().email("Invalid email").notRequired(), otp: Yup.string().required("OTP is required"), // Clinic details validation @@ -228,6 +245,20 @@ function YourDetailsForm() { .matches(/^[0-9]+$/, "Business Phone must be a valid number") .min(10, "Business Phone number should have 10 digits only.") .max(10, "Business Phone number should have 10 digits only."), + businessPhonePrefix: Yup.string().required( + "Business Phone Prefix is required" + ), + emergencyBusinessPhone: Yup.string() + .required("Emergency Business Phone is required") + .matches(/^[0-9]+$/, "Emergency Business Phone must be a valid number") + .min(10, "Emergency Business Phone number should have 10 digits only.") + .max(10, "Emergency Business Phone number should have 10 digits only."), + emergencyBusinessPhonePrefix: Yup.string().required( + "Emergency Business Phone Prefix is required" + ), + businessEmail: Yup.string() + .required("Business Email is required") + .email("Please enter valid Email Address"), businessFax: Yup.string() .required("Business Fax is required") .matches(/^[0-9]+$/, "Business Fax must be a valid number") @@ -239,35 +270,21 @@ function YourDetailsForm() { designation: Yup.string() .required("Designation is required") .typeError("Designation must be string"), - businessPhone: Yup.string() - .required("Clinic website is required") - .matches(WEBSITE_REGEX, "Please enter a valid URL"), - companyIndustry: Yup.string().required("Clinic Industry is required"), + pincode: Yup.string().required("Pincode is required"), state: Yup.string().required(), locality: Yup.string().required("City is required"), country: Yup.string().required("Country is required"), fullAddress: Yup.string() .required("Full Address is required") .typeError("Address must be string"), - companyPANImage: Yup.string() + companyABNImageNumber: Yup.string() .required("ABN number is required") .matches( ONLY_ALPHA_NUMERIC_ACCEPT_REGEX, "ABN Number must only contain numbers and letters" ) .length(ABN_NUMBER_LENGTH, "Enter valid ABN Number"), - companyTANNumber: Yup.string() - .required("Medicare number is required") - .matches( - ONLY_ALPHA_NUMERIC_ACCEPT_REGEX, - "Medicare Number must only contain numbers and letters" - ) - .length(MEDIICARE_NUMBER_LENGTH, "Enter valid Medicare Number"), - companyGSTImage: Yup.string().required("Clinic GST document is required"), - companyPANImage: Yup.string().required("Clinic ABN document is required"), - companyTANImage: Yup.string().required( - "Clinic MEDICARE document is required" - ), + companyABNImage: Yup.string().required("Clinic ABN document is required"), contract: Yup.string().required("Contract is required"), termsAccepted: Yup.boolean() .oneOf([true], "You must accept the terms and conditions") @@ -330,23 +347,22 @@ function YourDetailsForm() { const handleFormSubmit = async () => { dispatch(updateFormDetails(formik.values)); + const body = formatedData(formik.values); + console.log(body); - // const body = formatedData(formik.values); try { - // const response = await signup(body); - // if (response?.data?.error) { - // pushNotification(response?.data?.message, NOTIFICATION.ERROR); - // } else { - // pushNotification(response?.data?.message, NOTIFICATION.SUCCESS); - // dispatch(resetFormData()); - // navigate('/'); - // } - // pushNotification('Your request is submitted', NOTIFICATION.SUCCESS); - // dispatch(resetFormData()); - // navigate('/'); + // TODO: verify otp first + + const response = await signup(body); + if (response?.data?.error) { + pushNotification(response?.data?.message, NOTIFICATION.ERROR); + return; + } + pushNotification(response?.data?.message, NOTIFICATION.SUCCESS); + dispatch(resetFormData()); navigate("/auth/signup/payment"); } catch (error) { - // console.error('Error signing up:', error); + console.error('Error signing up:', error); } finally { formik.isSubmitting(false); } @@ -355,9 +371,7 @@ function YourDetailsForm() { // Initialize formik with a submission handler defined inline to avoid circular references const formik = useFormik({ initialValues: defaultFormData.current, - // validationSchema, - validateOnBlur: true, - validateOnChange: false, // Only validate on blur, not on every keystroke + validationSchema, onSubmit: handleFormSubmit, }); @@ -366,7 +380,7 @@ function YourDetailsForm() { if (formik?.values?.country !== defaultFormData.current.country) { formik.setFieldValue("pincode", ""); formik.setFieldValue("state", ""); - formik.setFieldValue("locality", ""); + // formik.setFieldValue("locality", ""); } }, [formik?.values?.country]); @@ -383,11 +397,11 @@ function YourDetailsForm() { useEffect(() => { if (Array.isArray(pincodeData) && pincodeData.length > 0) { formik.setFieldValue("state", pincodeData[0]?.state); - formik.setFieldValue("locality", selectedLocalityRef.current); + // formik.setFieldValue("locality", selectedLocalityRef.current); } else { setCountryOption(["Australia", "India"]); - formik.setFieldValue("state", ""); - formik.setFieldValue("locality", ""); + // formik.setFieldValue("state", ""); + // formik.setFieldValue("locality", ""); formik.setFieldError("pincode", "Invalid pincode"); } }, [pincodeData]); @@ -402,20 +416,34 @@ function YourDetailsForm() { aspect: 1 / 1, }); setLogoname(); - formik.setFieldValue("companyLogo", ""); + formik.setFieldValue("clinicLogo", ""); }; const handleFileUpload = async (value) => { - let formData = new FormData(); - formData.append("file", value); - formData.append("fileName", value?.name); try { - const data = await fileUpload(formData); - const imageUrl = data?.data?.data?.Key; - formik.setFieldValue("companyLogo", imageUrl); - return imageUrl; + // Store the file object directly in the form values + // We'll use this later for the actual upload after form submission + + const filePayload = { + folder: "assests", + file_name: value.name, + }; + + const presignedUrlResponseClinicLogo = await getPresignedUrl( + filePayload, + ); + + await uploadToS3( + value, + presignedUrlResponseClinicLogo?.data?.data?.api_url + ); + + formik.setFieldValue("clinicLogo", presignedUrlResponseClinicLogo?.data?.data?.key); + + // Return a temporary local URL for preview purposes + return URL.createObjectURL(value); } catch (error) { - pushNotification("Error while uploading file", NOTIFICATION.ERROR); + pushNotification("Error processing file", NOTIFICATION.ERROR); } }; @@ -508,7 +536,16 @@ function YourDetailsForm() { // Set uploaded file URL const setUploadedFileUrl = (documentName, fileUrl) => { - formik.setFieldValue(documentName, fileUrl); + console.log('Document Name:', documentName); + console.log('File URL:', fileUrl); + console.log('File URL Type:', typeof fileUrl); + + if (documentName && fileUrl !== undefined) { + formik.setFieldValue(documentName, fileUrl); + console.log('After setting value:', formik.values[documentName]); + } else { + console.error('Invalid parameters for setUploadedFileUrl:', { documentName, fileUrl }); + } }; // handleSaveAndNext will use formik's handleSubmit @@ -516,77 +553,66 @@ function YourDetailsForm() { // Helper function for formatted data function formatedData(inputData) { const data = { - name: inputData.companyName || "", - businessPhone: inputData.businessPhone || "", - emergencyBusinessPhone: inputData.emergencyBusinessPhone || "", - emergencyBusinessPhonePrefix: - inputData.emergencyBusinessPhonePrefix || "", - businessPhonePrefix: inputData.businessPhonePrefix || "", - businessFax: inputData.businessFax || "", - city: inputData.locality || "", - state: inputData.state || "", - logo: inputData.companyLogo || null, - street: inputData.fullAddress || "", - pinCode: inputData.pincode || "", - country: inputData.country || "", - locality: inputData.locality || "", - industryId: inputData.companyIndustry || 0, - companyAdmin: { - name: inputData.name || "", - email: inputData.email || "", - mobile: inputData.mobileNumber || "", - password: inputData.password || "", - designation: inputData.designation || "", + user: { + username: inputData.name, + email: inputData.email, + mobile: `${inputData.mobilePrefix ?? mobilePrefixOptions[0].name} ${ + inputData.mobileNumber + }`, + password: inputData.password, + clinicRole: inputData.designation, + userType: "clinic_admin", + }, + clinic: { + name: inputData.companyName, + address: inputData.fullAddress, + state: inputData.state, + phone: `${ + inputData.businessPhonePrefix ?? mobilePrefixOptions[0].name + } ${inputData.businessPhone}`, + emergencyPhone: `${ + inputData.emergencyBusinessPhonePrefix ?? mobilePrefixOptions[0].name + } ${inputData.emergencyBusinessPhone}`, + fax: inputData.businessFax, + email: inputData.businessEmail, + integration: inputData.practiceManagementSystem, + pms_id: inputData.practiceId, + practice_name: inputData.practiceName, + logo: inputData.clinicLogo || null, + country: inputData.country, + postal_code: inputData.pincode, + city: inputData.locality, + abn_number: inputData.companyABNImageNumber, + abn_doc: inputData.companyABNImage, + contract_doc: inputData.contract, }, - practiceId: inputData.practiceId || "", - practiceManagementSystem: inputData.practiceManagementSystem || "", - practiceName: inputData.practiceName || "", - companyUsers: inputData.billingUsers || "", - companyDocuments: [ - ...(inputData.companyLogo - ? [ - { - documentNumber: "LOGO", - fileURL: inputData.companyLogo, - documentType: "LOGO", - }, - ] - : []), - { - documentNumber: inputData.companyPANImage || "", - fileURL: inputData.companyPANImage || "", - documentType: "PAN", - }, - ], - contract: inputData.contract || "", }; return data; } - const handleAddUser = () => { - if ( - billingUsers?.includes(formik?.values?.currentEmail) || - formik?.values?.email === formik.values?.currentEmail - ) { - pushNotification("Email Address must be unique", NOTIFICATION.ERROR); + const handleSaveAndNext = async () => { + if (!otpField) { + pushNotification("Please verify OTP first", NOTIFICATION.ERROR); + otpButtonRef.current?.focus(); return; } - if (formik.values.currentEmail && billingUsers?.length < 5) { - setBillingUsers([...billingUsers, formik.values.currentEmail]); - formik.setFieldValue("currentEmail", ""); + + if (!testConnection) { + pushNotification("Please test the connection first", NOTIFICATION.ERROR); + // scroll to test connection button + // testConnectionRef.current?.scrollIntoView({ + // behavior: "smooth", + // }); + testConnectionRef.current?.focus(); + return; } - }; - const handleUserDelete = (index) => { - const updatedUsers = billingUsers.filter((_, i) => i !== index); - setBillingUsers(updatedUsers); - }; - - const handleSaveAndNext = async () => { const formikError = await formik.validateForm(formik.values); const errors = Object.keys(formikError); + console.log(errors) + if (errors.length) { // Find the first invalid field and focus it const firstErrorField = errors[0]; @@ -653,7 +679,7 @@ function YourDetailsForm() { className={classes.formRoot} > - + @@ -733,6 +759,7 @@ function YourDetailsForm() { color="info" disabled={!formik.values.email} onClick={handleOTPButton} + ref={otpButtonRef} > Request OTP @@ -1146,7 +1173,16 @@ function YourDetailsForm() { variant="outlined" name="emergencyBusinessPhone" value={formik.values.emergencyBusinessPhone} - onChange={formik.handleChange} + onChange={(e) => { + if (e.target.value.length <= 10) { + const value = + e.target.value?.match(/\d+/g) || ""; + formik.setFieldValue( + "emergencyBusinessPhone", + value?.toString() + ); + } + }} onBlur={formik.handleBlur} InputProps={{ type: "text", @@ -1253,16 +1289,7 @@ function YourDetailsForm() { variant="outlined" name="businessEmail" value={formik.values.businessEmail} - onChange={(e) => { - if (e.target.value.length <= 10) { - const value = - e.target.value?.match(/\d+/g) || ""; - formik.setFieldValue( - "businessEmail", - value?.toString() - ); - } - }} + onChange={formik.handleChange} onBlur={formik.handleBlur} InputProps={{ type: "email", @@ -1307,8 +1334,8 @@ function YourDetailsForm() { } > {integrationOptions.map((option) => ( - - {option} + + {option.name} ))} @@ -1372,6 +1399,7 @@ function YourDetailsForm() { variant="contained" color="primary" onClick={handleTestConnection} + ref={testConnectionRef} > Test Connection @@ -1381,7 +1409,7 @@ function YourDetailsForm() { Add Business Logo - {(logoImage || formik.values.companyLogo) && ( + {(logoImage || formik.values.clinicLogo) && (
+ + ({ ...state, diff --git a/src/views/User/index.jsx b/src/views/User/index.jsx index 9c26b40..0343b6d 100644 --- a/src/views/User/index.jsx +++ b/src/views/User/index.jsx @@ -80,7 +80,7 @@ function Users() { const [roles, setRoles] = useState(); const [isAdmin, setIsAdmin] = useState(); - const isBsAdmin = useSelector((state) => state?.login?.user?.isBsAdmin); + const isSuperAdmin = useSelector((state) => state?.login?.user?.isSuperAdmin); /* ----------------- Get Users ----------------- */ const getData = async (filters) => {