From 91f272962fb54098c85e6b2cc58f5ed15263a4d9 Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sat, 11 Nov 2023 14:26:32 +0200 Subject: [PATCH 01/15] pulling main and putting my funcs --- client/package-lock.json | 178 +++++++++++++++++++-- client/yarn.lock | 84 ++++++++-- server/src/routes/me.route.ts | 27 +++- server/src/services/appointment.service.ts | 84 +++++++++- server/src/services/doctor.service.ts | 29 +++- 5 files changed, 372 insertions(+), 30 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 1d9b8a6..bc74502 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -29,8 +29,9 @@ "react-apexcharts": "^1.4.1", "react-dom": "^18.2.0", "react-helmet-async": "^1.3.0", - "react-hook-form": "^7.47.0", + "react-hook-form": "^7.48.2", "react-pdf-viewer": "^0.1.0", + "react-query": "^3.39.3", "react-router-dom": "^6.16.0", "simplebar-react": "^3.2.4" }, @@ -2392,7 +2393,6 @@ "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true, "engines": { "node": ">=0.6" } @@ -2551,6 +2551,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -6789,6 +6804,11 @@ "set-function-name": "^2.0.1" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7146,6 +7166,15 @@ "node": ">=0.10.0" } }, + "node_modules/match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -7506,6 +7535,11 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -7724,6 +7758,14 @@ "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", "optional": true }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -8171,6 +8213,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -9085,9 +9132,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.47.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.47.0.tgz", - "integrity": "sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==", + "version": "7.48.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz", + "integrity": "sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A==", "engines": { "node": ">=12.22.0" }, @@ -9169,6 +9216,31 @@ "object-assign": "^4.1.1" } }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.16.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", @@ -9376,6 +9448,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -9508,7 +9585,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "devOptional": true, "dependencies": { "glob": "^7.1.3" }, @@ -11215,6 +11291,15 @@ "node": ">= 10.0.0" } }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -14528,8 +14613,7 @@ "big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==" }, "big.js": { "version": "5.2.2", @@ -14658,6 +14742,21 @@ "fill-range": "^7.0.1" } }, + "broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "requires": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -17911,6 +18010,11 @@ "set-function-name": "^2.0.1" } }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18186,6 +18290,15 @@ "object-visit": "^1.0.0" } }, + "match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "requires": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -18481,6 +18594,11 @@ "picomatch": "^2.3.1" } }, + "microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -18643,6 +18761,14 @@ "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", "optional": true }, + "nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "requires": { + "big-integer": "^1.6.16" + } + }, "nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -18986,6 +19112,11 @@ "es-abstract": "^1.20.4" } }, + "oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -19666,9 +19797,9 @@ } }, "react-hook-form": { - "version": "7.47.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.47.0.tgz", - "integrity": "sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==", + "version": "7.48.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz", + "integrity": "sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A==", "requires": {} }, "react-is": { @@ -19729,6 +19860,16 @@ } } }, + "react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "requires": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + } + }, "react-router": { "version": "6.16.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", @@ -19881,6 +20022,11 @@ "set-function-name": "^2.0.0" } }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -19977,7 +20123,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "devOptional": true, "requires": { "glob": "^7.1.3" } @@ -21324,6 +21469,15 @@ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, + "unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "requires": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/client/yarn.lock b/client/yarn.lock index 2e04607..f90fd3e 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -40,7 +40,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.6", "@babel/runtime@^7.23.1", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.6", "@babel/runtime@^7.23.1", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7": version "7.23.2" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz" integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== @@ -1084,7 +1084,7 @@ batch@0.6.1: resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz" integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== -big-integer@^1.6.44: +big-integer@^1.6.16, big-integer@^1.6.44: version "1.6.51" resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== @@ -1199,6 +1199,20 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" @@ -2014,7 +2028,7 @@ detect-libc@^2.0.0: resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz" integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== -detect-node@^2.0.4: +detect-node@^2.0.4, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== @@ -3928,6 +3942,11 @@ iterator.prototype@^1.1.2: reflect.getprototypeof "^1.0.4" set-function-name "^2.0.1" +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -4220,6 +4239,14 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +match-sorter@^6.0.2: + version "6.3.1" + resolved "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz" + integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz" @@ -4330,6 +4357,11 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz" @@ -4498,6 +4530,13 @@ nan@^2.17.0: resolved "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz" integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz" + integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA== + dependencies: + big-integer "^1.6.16" + nanoid@^3.3.6: version "3.3.6" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz" @@ -4777,6 +4816,11 @@ object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== + obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" @@ -5354,10 +5398,10 @@ react-helmet-async@^1.3.0: react-fast-compare "^3.2.0" shallowequal "^1.1.0" -react-hook-form@^7.47.0: - version "7.47.0" - resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.47.0.tgz" - integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg== +react-hook-form@^7.48.2: + version "7.48.2" + resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz" + integrity sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A== react-is@^16.13.1: version "16.13.1" @@ -5385,6 +5429,15 @@ react-pdf-viewer@^0.1.0: webpack "^3.10.0" webpack-dev-server "^2.9.7" +react-query@^3.39.3: + version "3.39.3" + resolved "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz" + integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + react-router-dom@^6.16.0: version "6.16.0" resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz" @@ -5419,7 +5472,7 @@ react@^16.14.0, react@^16.2.0: object-assign "^4.1.1" prop-types "^15.6.2" -"react@^16.6.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18", "react@^17.0.0 || ^18.0.0", react@^18.2.0, react@>=0.13, react@>=16, react@>=16.6.0, react@>=16.8, react@>=16.8.0: +"react@^16.6.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.2.0, react@>=0.13, react@>=16, react@>=16.6.0, react@>=16.8, react@>=16.8.0: version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -5606,6 +5659,11 @@ regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.1: define-properties "^1.2.0" set-function-name "^2.0.0" +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz" + integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz" @@ -5707,7 +5765,7 @@ rimraf@^2.2.8: dependencies: glob "^7.1.3" -rimraf@^3.0.2: +rimraf@^3.0.2, rimraf@3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -6761,6 +6819,14 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unload@2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + unpipe@~1.0.0, unpipe@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" diff --git a/server/src/routes/me.route.ts b/server/src/routes/me.route.ts index 268c12a..40463f2 100644 --- a/server/src/routes/me.route.ts +++ b/server/src/routes/me.route.ts @@ -9,7 +9,11 @@ import { getPrescriptions, addFamilyMember, getFamily, - getHealthRecords + getHealthRecords, + getPastAppointments, + filterAppointments, + getUpcomingAppointments, + viewWallet } from '../services'; const router = express.Router(); @@ -28,16 +32,29 @@ router.use(isAuthorized('Doctor', 'Patient')); // get my appointments router.get('/appointments', (req: Request, res: Response) => { - // user's id field depends on the role - const userID = req.decoded.role === 'Patient' ? 'patientID' : 'doctorID'; - controller(res)(getAppointments)({ ...req.query, [userID]: req.decoded.id }); + if (req.query.s === 'Upcoming') { + controller(res)(getUpcomingAppointments)(req.decoded.id, req.decoded.role); + } + else if (req.query.s === 'Completed') { + controller(res)(getPastAppointments)(req.decoded.id, req.decoded.role) + } + else if (req.query.s === 'filter') { + // user's id field depends on the role + const userId = req.decoded.role === 'Patient' ? 'patientID' : 'doctorID'; + controller(res)(filterAppointments)({ ...req.query, [userId]: req.decoded.id, role: req.decoded.role }); + } + else { + // user's id field depends on the role + const userID = req.decoded.role === 'Patient' ? 'patientID' : 'doctorID'; + controller(res)(getAppointments)({ ...req.query, [userID]: req.decoded.id }); + } }); router.post('/appointments', (req: Request, res: Response) => { // controller(res)()(); }); router.get('/wallet', (req: Request, res: Response) => { - // controller(res)()(); + controller(res)(viewWallet)(req.decoded.id, req.decoded.role); }); // Doctor Routes diff --git a/server/src/services/appointment.service.ts b/server/src/services/appointment.service.ts index 0ec7821..af6d06b 100644 --- a/server/src/services/appointment.service.ts +++ b/server/src/services/appointment.service.ts @@ -1,4 +1,4 @@ -import { Appointment, Doctor } from '../models'; +import { Appointment, Doctor , Patient} from '../models'; import { StatusCodes } from 'http-status-codes'; import mongoose from 'mongoose'; import { HttpError } from '../utils'; @@ -33,4 +33,84 @@ const createAppointment = async (patientID: String, doctorID: String, body: any) }; }; -export { getAppointments, createAppointment }; +// Function to get upcoming appointments for a user (patient or doctor) req 45 +const getUpcomingAppointments = async (userId: string, role: string) => { + if (role === 'doctor' || role === 'Doctor') { + if (await Doctor.exists({ _id: userId })) { + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: await Appointment.find({ doctorID: userId, startDate: { $gte: new Date() } }), + }; + } + } else if (role === 'patient' || role === 'Patient') { + if (await Patient.exists({ _id: userId })) { + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: await Appointment.find({ patientID: userId, startDate: { $gte: new Date() } }), + }; + } + } + else { + throw new HttpError( + StatusCodes.BAD_REQUEST, + 'Role is neither a doctor nor a patient or wrong user id' + ); + } +}; + +// Function to get past appointments for a user (patient or doctor) contiue req 45 +const getPastAppointments = async (userId: string, role: string) => { + if (role === 'doctor' || role === 'Doctor') { + if (await Doctor.exists({ _id: userId })) { + return { + status: StatusCodes.OK, + message: 'Past appointments retrieved successfully', + result: await Appointment.find({ doctorID: userId, endDate: { $lt: new Date() } }), + }; + } + } + else if (role === 'patient' || role === 'Patient') { + if (await Patient.exists({ _id: userId })) { + return { + status: StatusCodes.OK, + message: 'Past appointments retrieved successfully', + result: await Appointment.find({ patientID: userId, endDate: { $lt: new Date() } }), + }; + } + } + else { + throw new HttpError( + StatusCodes.BAD_REQUEST, + 'Role is neither a doctor nor a patient or wrong user id' + ); + } +}; + +// Function to filter appointments by date or status (upcoming, completed, cancelled, rescheduled) req 46 +const filterAppointments = async (query: any) => { + const startDate = query.startDate ? new Date(query.startDate) : new Date('1000-01-01T00:00:00.000Z'); + const endDate = query.endDate ? new Date(query.endDate) : new Date('9999-01-01T00:00:00.000Z'); + const status = query.status ? query.status : { $in: ['Upcoming', 'Completed', 'Cancelled', 'Rescheduled'] }; + if (query.role === 'doctor' || query.role === 'Doctor') { + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: await Appointment.find({ doctorID: query.doctorID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }), + }; + } else if (query.role === 'patient' || query.role === 'Patient') { + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: await Appointment.find({ patientID: query.patientID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }), + }; + } else { + throw new HttpError( + StatusCodes.BAD_REQUEST, + 'User is neither a doctor nor a patient' + ); + } +}; + +export { getAppointments, createAppointment , getUpcomingAppointments, getPastAppointments, filterAppointments }; diff --git a/server/src/services/doctor.service.ts b/server/src/services/doctor.service.ts index 2d5918b..e90172c 100644 --- a/server/src/services/doctor.service.ts +++ b/server/src/services/doctor.service.ts @@ -1,7 +1,7 @@ const { v4: uuidv4 } = require('uuid'); import { HttpError } from '../utils'; import StatusCodes from 'http-status-codes'; -import { User, Contract, Appointment, IPatient, IDoctor, Doctor,Request } from '../models'; +import { User, Contract, Appointment, IPatient, IDoctor, Doctor,Request, Patient } from '../models'; const getMyPatients = async (query: any) => { const appointments = await Appointment.find(query).distinct('patientID').select('patientID').populate('patientID'); @@ -181,4 +181,29 @@ const viewAvailableAppointments = async (doctorID: string) => { }; }; -export { getDoctors, getMyPatients, viewAvailableAppointments, saveRegistrationFiles }; \ No newline at end of file +// View the amount in my wallet req 67 for patient and doctor +const viewWallet = async (userId: string, role: string) => { + let user = null; + let userType = ""; + + if (role === 'patient' || role === 'Patient') { + user = await Patient.findById(userId); + userType = 'Patients'; + } else if (role === 'doctor' || role === 'Doctor') { + user = await Doctor.findById(userId); + userType = 'Doctor'; + } + if (!user) { + throw new HttpError( + StatusCodes.NOT_FOUND, + `${userType} not found` + ); + } + + return { + result: user.wallet, + status: StatusCodes.OK, + message: `Successfully retrieved ${userType}'s wallet` + }; +}; +export { getDoctors, getMyPatients, viewAvailableAppointments, saveRegistrationFiles , viewWallet}; \ No newline at end of file From e7c5055ddceb00d99d90a3cbb3990a01e07e0d8d Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sat, 11 Nov 2023 18:39:42 +0200 Subject: [PATCH 02/15] changing user page to appointment page with getpast or get upcoming --- client/src/sections/user/user-table-head.jsx | 2 +- client/src/sections/user/user-table-row.jsx | 78 ++++------ client/src/sections/user/view/user-view.jsx | 154 +++++++++++-------- 3 files changed, 122 insertions(+), 112 deletions(-) diff --git a/client/src/sections/user/user-table-head.jsx b/client/src/sections/user/user-table-head.jsx index cdb9a47..c48a40a 100644 --- a/client/src/sections/user/user-table-head.jsx +++ b/client/src/sections/user/user-table-head.jsx @@ -70,4 +70,4 @@ UserTableHead.propTypes = { numSelected: PropTypes.number, onRequestSort: PropTypes.func, onSelectAllClick: PropTypes.func, -}; +}; \ No newline at end of file diff --git a/client/src/sections/user/user-table-row.jsx b/client/src/sections/user/user-table-row.jsx index f21023e..5f2fc9f 100644 --- a/client/src/sections/user/user-table-row.jsx +++ b/client/src/sections/user/user-table-row.jsx @@ -1,6 +1,5 @@ import { useState } from 'react'; import PropTypes from 'prop-types'; - import Stack from '@mui/material/Stack'; import Avatar from '@mui/material/Avatar'; import Popover from '@mui/material/Popover'; @@ -10,20 +9,19 @@ import MenuItem from '@mui/material/MenuItem'; import TableCell from '@mui/material/TableCell'; import Typography from '@mui/material/Typography'; import IconButton from '@mui/material/IconButton'; - import Label from 'src/components/label'; import Iconify from 'src/components/iconify'; -// ---------------------------------------------------------------------- - export default function UserTableRow({ selected, - name, - avatarUrl, - company, - role, - isVerified, + _id, + doctorID, + patientID, status, + sessionPrice, + startDate, + endDate, + isFollowUp, handleClick, }) { const [open, setOpen] = useState(null); @@ -45,61 +43,41 @@ export default function UserTableRow({ - + - {name} + {doctorID} - {company} + {patientID} - {role} + {status} - {isVerified ? 'Yes' : 'No'} + {sessionPrice} - - - + {startDate} - - - - - - + {endDate} + + {isFollowUp ? 'Yes' : 'No'} - - - - Edit - + + - - - Delete - - ); } UserTableRow.propTypes = { - avatarUrl: PropTypes.any, - company: PropTypes.any, - handleClick: PropTypes.func, - isVerified: PropTypes.any, - name: PropTypes.any, - role: PropTypes.any, - selected: PropTypes.any, + _id: PropTypes.string.isRequired, + doctorID: PropTypes.string, + patientID: PropTypes.string, status: PropTypes.string, -}; + sessionPrice: PropTypes.number, + startDate: PropTypes.string, + endDate: PropTypes.string, + isFollowUp: PropTypes.bool, + selected: PropTypes.bool, + handleClick: PropTypes.func, +}; \ No newline at end of file diff --git a/client/src/sections/user/view/user-view.jsx b/client/src/sections/user/view/user-view.jsx index e3a84eb..965a493 100644 --- a/client/src/sections/user/view/user-view.jsx +++ b/client/src/sections/user/view/user-view.jsx @@ -1,5 +1,4 @@ -import { useState } from 'react'; - +import { useState, useEffect } from 'react'; import Card from '@mui/material/Card'; import Stack from '@mui/material/Stack'; import Table from '@mui/material/Table'; @@ -9,33 +8,35 @@ import TableBody from '@mui/material/TableBody'; import Typography from '@mui/material/Typography'; import TableContainer from '@mui/material/TableContainer'; import TablePagination from '@mui/material/TablePagination'; - -import { users } from 'src/_mock/user'; - -import Iconify from 'src/components/iconify'; -import Scrollbar from 'src/components/scrollbar'; - import TableNoData from '../table-no-data'; import UserTableRow from '../user-table-row'; import UserTableHead from '../user-table-head'; import TableEmptyRows from '../table-empty-rows'; -import UserTableToolbar from '../user-table-toolbar'; -import { emptyRows, applyFilter, getComparator } from '../utils'; - -// ---------------------------------------------------------------------- +import { emptyRows, getComparator } from '../utils'; +import { axiosInstance } from '../../../utils/axiosInstance'; +import Iconify from 'src/components/iconify'; +import Scrollbar from 'src/components/scrollbar'; export default function UserPage() { + const [appointments, setAppointments] = useState([]); const [page, setPage] = useState(0); - const [order, setOrder] = useState('asc'); - const [selected, setSelected] = useState([]); + const [orderBy, setOrderBy] = useState('doctorID'); // Set default ordering based on doctorID + const [rowsPerPage, setRowsPerPage] = useState(5); - const [orderBy, setOrderBy] = useState('name'); - - const [filterName, setFilterName] = useState(''); + useEffect(() => { + const fetchAppointments = async () => { + try { + const response = await axiosInstance.get('/me/appointments'); + setAppointments(response.data.result); + } catch (error) { + console.error('Error fetching appointments:', error); + } + }; - const [rowsPerPage, setRowsPerPage] = useState(5); + fetchAppointments(); + }, []); // Fetch appointments on component mount const handleSort = (event, id) => { const isAsc = orderBy === id && order === 'asc'; @@ -47,18 +48,18 @@ export default function UserPage() { const handleSelectAllClick = (event) => { if (event.target.checked) { - const newSelecteds = users.map((n) => n.name); + const newSelecteds = appointments.map((n) => n.doctorID); setSelected(newSelecteds); return; } setSelected([]); }; - const handleClick = (event, name) => { - const selectedIndex = selected.indexOf(name); + const handleClick = (event, doctorID) => { + const selectedIndex = selected.indexOf(doctorID); let newSelected = []; if (selectedIndex === -1) { - newSelected = newSelected.concat(selected, name); + newSelected = newSelected.concat(selected, doctorID); } else if (selectedIndex === 0) { newSelected = newSelected.concat(selected.slice(1)); } else if (selectedIndex === selected.length - 1) { @@ -81,78 +82,109 @@ export default function UserPage() { setRowsPerPage(parseInt(event.target.value, 10)); }; - const handleFilterByName = (event) => { - setPage(0); - setFilterName(event.target.value); + const handleGetUpcomingAppointments = async () => { + try { + const response = await axiosInstance.get('/me/appointments?s=Upcoming'); + setAppointments(response.data.result); + } catch (error) { + console.error('Error fetching upcoming appointments:', error); + } }; - const dataFiltered = applyFilter({ - inputData: users, - comparator: getComparator(order, orderBy), - filterName, - }); - - const notFound = !dataFiltered.length && !!filterName; - + const handleGetPastAppointments = async () => { + try { + const response = await axiosInstance.get('/me/appointments?s=Completed'); + setAppointments(response.data.result); + } catch (error) { + console.error('Error fetching past appointments:', error); + } + }; + + const handleGetAllAppointments = async () => { + try { + const response = await axiosInstance.get('/me/appointments'); + setAppointments(response.data.result); + } catch (error) { + console.error('Error fetching all appointments:', error); + } + }; return ( - Users + Appointments + + - + - - - {dataFiltered + {appointments .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((row) => ( handleClick(event, row.name)} + sessionPrice={row.sessionPrice} + startDate={row.startDate} + endDate={row.endDate} + isFollowUp={row.isFollowUp} + selected={selected.indexOf(row.doctorID) !== -1} + handleClick={(event) => handleClick(event, row.doctorID)} /> ))} - - {notFound && }
@@ -161,7 +193,7 @@ export default function UserPage() { Date: Sat, 11 Nov 2023 18:52:21 +0200 Subject: [PATCH 03/15] modifying appointments get , past , upcoming , filter to get patient name and doctor name --- server/src/services/appointment.service.ts | 62 ++++++++++++++-------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/server/src/services/appointment.service.ts b/server/src/services/appointment.service.ts index af6d06b..b67f643 100644 --- a/server/src/services/appointment.service.ts +++ b/server/src/services/appointment.service.ts @@ -7,7 +7,9 @@ const getAppointments = async (query: any) => { if (query.startDate) query.startDate = { $gte: query.startDate }; if (query.endDate) query.endDate = { $lte: query.endDate }; - const appointments = await Appointment.find(query); + const appointments = await Appointment.find(query) + .populate('doctorID', 'name') // Populate the doctorID field with the name property + .populate('patientID', 'name'); // Populate the patientID field with the name property if (!appointments) throw new HttpError(StatusCodes.NOT_FOUND, 'No upcoming appointments'); @@ -18,6 +20,7 @@ const getAppointments = async (query: any) => { }; }; + const createAppointment = async (patientID: String, doctorID: String, body: any) => { const doctor = await Doctor.findById(doctorID); if (!doctor) throw new HttpError(StatusCodes.NOT_FOUND, 'Doctor not found'); @@ -37,22 +40,29 @@ const createAppointment = async (patientID: String, doctorID: String, body: any) const getUpcomingAppointments = async (userId: string, role: string) => { if (role === 'doctor' || role === 'Doctor') { if (await Doctor.exists({ _id: userId })) { + const appointments = await Appointment.find({ doctorID: userId, startDate: { $gte: new Date() } }) + .populate('doctorID', 'name') // Populate the doctorID field with the name property + .populate('patientID', 'name'); // Populate the patientID field with the name property + return { status: StatusCodes.OK, message: 'Appointments retrieved successfully', - result: await Appointment.find({ doctorID: userId, startDate: { $gte: new Date() } }), + result: appointments, }; } } else if (role === 'patient' || role === 'Patient') { if (await Patient.exists({ _id: userId })) { + const appointments = await Appointment.find({ patientID: userId, startDate: { $gte: new Date() } }) + .populate('doctorID', 'name') // Populate the doctorID field with the name property + .populate('patientID', 'name'); // Populate the patientID field with the name property + return { status: StatusCodes.OK, message: 'Appointments retrieved successfully', - result: await Appointment.find({ patientID: userId, startDate: { $gte: new Date() } }), + result: appointments, }; } - } - else { + } else { throw new HttpError( StatusCodes.BAD_REQUEST, 'Role is neither a doctor nor a patient or wrong user id' @@ -60,6 +70,7 @@ const getUpcomingAppointments = async (userId: string, role: string) => { } }; + // Function to get past appointments for a user (patient or doctor) contiue req 45 const getPastAppointments = async (userId: string, role: string) => { if (role === 'doctor' || role === 'Doctor') { @@ -67,20 +78,22 @@ const getPastAppointments = async (userId: string, role: string) => { return { status: StatusCodes.OK, message: 'Past appointments retrieved successfully', - result: await Appointment.find({ doctorID: userId, endDate: { $lt: new Date() } }), + result: await Appointment.find({ doctorID: userId, endDate: { $lt: new Date() } }) + .populate('doctorID', 'name') // Populate the doctorID field with the name property + .populate('patientID', 'name'), // Populate the patientID field with the name property }; } - } - else if (role === 'patient' || role === 'Patient') { + } else if (role === 'patient' || role === 'Patient') { if (await Patient.exists({ _id: userId })) { return { status: StatusCodes.OK, message: 'Past appointments retrieved successfully', - result: await Appointment.find({ patientID: userId, endDate: { $lt: new Date() } }), + result: await Appointment.find({ patientID: userId, endDate: { $lt: new Date() } }) + .populate('doctorID', 'name') // Populate the doctorID field with the name property + .populate('patientID', 'name'), // Populate the patientID field with the name property }; } - } - else { + } else { throw new HttpError( StatusCodes.BAD_REQUEST, 'Role is neither a doctor nor a patient or wrong user id' @@ -88,29 +101,36 @@ const getPastAppointments = async (userId: string, role: string) => { } }; + // Function to filter appointments by date or status (upcoming, completed, cancelled, rescheduled) req 46 const filterAppointments = async (query: any) => { const startDate = query.startDate ? new Date(query.startDate) : new Date('1000-01-01T00:00:00.000Z'); const endDate = query.endDate ? new Date(query.endDate) : new Date('9999-01-01T00:00:00.000Z'); const status = query.status ? query.status : { $in: ['Upcoming', 'Completed', 'Cancelled', 'Rescheduled'] }; + + let result; + if (query.role === 'doctor' || query.role === 'Doctor') { - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: await Appointment.find({ doctorID: query.doctorID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }), - }; + result = await Appointment.find({ doctorID: query.doctorID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }) + .populate('doctorID', 'name') // Populate the doctorID field with the name property + .populate('patientID', 'name'); // Populate the patientID field with the name property } else if (query.role === 'patient' || query.role === 'Patient') { - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: await Appointment.find({ patientID: query.patientID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }), - }; + result = await Appointment.find({ patientID: query.patientID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }) + .populate('doctorID', 'name') // Populate the doctorID field with the name property + .populate('patientID', 'name'); // Populate the patientID field with the name property } else { throw new HttpError( StatusCodes.BAD_REQUEST, 'User is neither a doctor nor a patient' ); } + + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: result, + }; }; + export { getAppointments, createAppointment , getUpcomingAppointments, getPastAppointments, filterAppointments }; From 28fc6272392d6ff5deb7c48a8a3d3d661422112d Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sat, 11 Nov 2023 19:39:32 +0200 Subject: [PATCH 04/15] returning the appointment methods to get ids --- server/src/services/appointment.service.ts | 59 ++++++++-------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/server/src/services/appointment.service.ts b/server/src/services/appointment.service.ts index b67f643..fc3995a 100644 --- a/server/src/services/appointment.service.ts +++ b/server/src/services/appointment.service.ts @@ -7,9 +7,7 @@ const getAppointments = async (query: any) => { if (query.startDate) query.startDate = { $gte: query.startDate }; if (query.endDate) query.endDate = { $lte: query.endDate }; - const appointments = await Appointment.find(query) - .populate('doctorID', 'name') // Populate the doctorID field with the name property - .populate('patientID', 'name'); // Populate the patientID field with the name property + const appointments = await Appointment.find(query); if (!appointments) throw new HttpError(StatusCodes.NOT_FOUND, 'No upcoming appointments'); @@ -40,29 +38,22 @@ const createAppointment = async (patientID: String, doctorID: String, body: any) const getUpcomingAppointments = async (userId: string, role: string) => { if (role === 'doctor' || role === 'Doctor') { if (await Doctor.exists({ _id: userId })) { - const appointments = await Appointment.find({ doctorID: userId, startDate: { $gte: new Date() } }) - .populate('doctorID', 'name') // Populate the doctorID field with the name property - .populate('patientID', 'name'); // Populate the patientID field with the name property - return { status: StatusCodes.OK, message: 'Appointments retrieved successfully', - result: appointments, + result: await Appointment.find({ doctorID: userId, startDate: { $gte: new Date() } }), }; } } else if (role === 'patient' || role === 'Patient') { if (await Patient.exists({ _id: userId })) { - const appointments = await Appointment.find({ patientID: userId, startDate: { $gte: new Date() } }) - .populate('doctorID', 'name') // Populate the doctorID field with the name property - .populate('patientID', 'name'); // Populate the patientID field with the name property - return { status: StatusCodes.OK, message: 'Appointments retrieved successfully', - result: appointments, + result: await Appointment.find({ patientID: userId, startDate: { $gte: new Date() } }), }; } - } else { + } + else { throw new HttpError( StatusCodes.BAD_REQUEST, 'Role is neither a doctor nor a patient or wrong user id' @@ -78,22 +69,20 @@ const getPastAppointments = async (userId: string, role: string) => { return { status: StatusCodes.OK, message: 'Past appointments retrieved successfully', - result: await Appointment.find({ doctorID: userId, endDate: { $lt: new Date() } }) - .populate('doctorID', 'name') // Populate the doctorID field with the name property - .populate('patientID', 'name'), // Populate the patientID field with the name property + result: await Appointment.find({ doctorID: userId, endDate: { $lt: new Date() } }), }; } - } else if (role === 'patient' || role === 'Patient') { + } + else if (role === 'patient' || role === 'Patient') { if (await Patient.exists({ _id: userId })) { return { status: StatusCodes.OK, message: 'Past appointments retrieved successfully', - result: await Appointment.find({ patientID: userId, endDate: { $lt: new Date() } }) - .populate('doctorID', 'name') // Populate the doctorID field with the name property - .populate('patientID', 'name'), // Populate the patientID field with the name property + result: await Appointment.find({ patientID: userId, endDate: { $lt: new Date() } }), }; } - } else { + } + else { throw new HttpError( StatusCodes.BAD_REQUEST, 'Role is neither a doctor nor a patient or wrong user id' @@ -107,30 +96,24 @@ const filterAppointments = async (query: any) => { const startDate = query.startDate ? new Date(query.startDate) : new Date('1000-01-01T00:00:00.000Z'); const endDate = query.endDate ? new Date(query.endDate) : new Date('9999-01-01T00:00:00.000Z'); const status = query.status ? query.status : { $in: ['Upcoming', 'Completed', 'Cancelled', 'Rescheduled'] }; - - let result; - if (query.role === 'doctor' || query.role === 'Doctor') { - result = await Appointment.find({ doctorID: query.doctorID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }) - .populate('doctorID', 'name') // Populate the doctorID field with the name property - .populate('patientID', 'name'); // Populate the patientID field with the name property + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: await Appointment.find({ doctorID: query.doctorID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }), + }; } else if (query.role === 'patient' || query.role === 'Patient') { - result = await Appointment.find({ patientID: query.patientID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }) - .populate('doctorID', 'name') // Populate the doctorID field with the name property - .populate('patientID', 'name'); // Populate the patientID field with the name property + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: await Appointment.find({ patientID: query.patientID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }), + }; } else { throw new HttpError( StatusCodes.BAD_REQUEST, 'User is neither a doctor nor a patient' ); } - - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: result, - }; }; - export { getAppointments, createAppointment , getUpcomingAppointments, getPastAppointments, filterAppointments }; From 0766a72ed441a68561b3ffaf12a872d47afb4d6a Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sat, 11 Nov 2023 20:19:59 +0200 Subject: [PATCH 05/15] appointments with names is now fully working , filtering is still not done --- client/src/sections/user/user-table-head.jsx | 13 +- client/src/sections/user/user-table-row.jsx | 17 +- client/src/sections/user/view/user-view.jsx | 14 +- server/src/services/appointment.service.ts | 204 ++++++++++++++----- 4 files changed, 181 insertions(+), 67 deletions(-) diff --git a/client/src/sections/user/user-table-head.jsx b/client/src/sections/user/user-table-head.jsx index c48a40a..43254f5 100644 --- a/client/src/sections/user/user-table-head.jsx +++ b/client/src/sections/user/user-table-head.jsx @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; - import Box from '@mui/material/Box'; import TableRow from '@mui/material/TableRow'; import Checkbox from '@mui/material/Checkbox'; @@ -66,8 +65,16 @@ UserTableHead.propTypes = { order: PropTypes.oneOf(['asc', 'desc']), orderBy: PropTypes.string, rowCount: PropTypes.number, - headLabel: PropTypes.array, + headLabel: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + align: PropTypes.string, + width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + minWidth: PropTypes.number, + }) + ), numSelected: PropTypes.number, onRequestSort: PropTypes.func, onSelectAllClick: PropTypes.func, -}; \ No newline at end of file +}; diff --git a/client/src/sections/user/user-table-row.jsx b/client/src/sections/user/user-table-row.jsx index 5f2fc9f..a23c940 100644 --- a/client/src/sections/user/user-table-row.jsx +++ b/client/src/sections/user/user-table-row.jsx @@ -15,8 +15,8 @@ import Iconify from 'src/components/iconify'; export default function UserTableRow({ selected, _id, - doctorID, - patientID, + patientName, + doctorName, status, sessionPrice, startDate, @@ -43,14 +43,13 @@ export default function UserTableRow({ - - {doctorID} + {doctorName} - {patientID} + {patientName} {status} @@ -61,18 +60,16 @@ export default function UserTableRow({ {endDate} {isFollowUp ? 'Yes' : 'No'} - - - ); } + UserTableRow.propTypes = { _id: PropTypes.string.isRequired, - doctorID: PropTypes.string, - patientID: PropTypes.string, + doctorName: PropTypes.string, + patientName: PropTypes.string, status: PropTypes.string, sessionPrice: PropTypes.number, startDate: PropTypes.string, diff --git a/client/src/sections/user/view/user-view.jsx b/client/src/sections/user/view/user-view.jsx index 965a493..49b7323 100644 --- a/client/src/sections/user/view/user-view.jsx +++ b/client/src/sections/user/view/user-view.jsx @@ -99,7 +99,7 @@ export default function UserPage() { console.error('Error fetching past appointments:', error); } }; - + const handleGetAllAppointments = async () => { try { const response = await axiosInstance.get('/me/appointments'); @@ -141,7 +141,7 @@ export default function UserPage() { - + @@ -169,8 +169,8 @@ export default function UserPage() { { const appointments = await Appointment.find(query); - if (!appointments) throw new HttpError(StatusCodes.NOT_FOUND, 'No upcoming appointments'); + if (!appointments || appointments.length === 0) { + throw new HttpError(StatusCodes.NOT_FOUND, 'No upcoming appointments'); + } + + const doctorIds = appointments.map(appointment => appointment.doctorID); + const patientIds = appointments.map(appointment => appointment.patientID); + + // Fetch doctors and patients based on the extracted ids + const doctors = await Doctor.find({ _id: { $in: doctorIds } }); + const patients = await Patient.find({ _id: { $in: patientIds } }); + + const doctorMap = new Map(doctors.map(doctor => [doctor._id.toString(), doctor])); + const patientMap = new Map(patients.map(patient => [patient._id.toString(), patient])); + + const formattedAppointments = appointments.map(appointment => { + const doctor = doctorMap.get(appointment.doctorID.toString()); + const patient = patientMap.get(appointment.patientID.toString()); + + // Accessing the properties directly without _doc + return { + patientName: patient ? patient.get('name') : 'Unknown Patient', + doctorName: doctor ? doctor.get('name') : 'Unknown Doctor', + ...appointment.toObject(), + }; + }); return { status: StatusCodes.OK, message: 'Appointments retrieved successfully', - result: appointments + result: formattedAppointments, }; }; @@ -36,84 +60,170 @@ const createAppointment = async (patientID: String, doctorID: String, body: any) // Function to get upcoming appointments for a user (patient or doctor) req 45 const getUpcomingAppointments = async (userId: string, role: string) => { - if (role === 'doctor' || role === 'Doctor') { + let appointments; + + if (role.toLowerCase() === 'doctor') { if (await Doctor.exists({ _id: userId })) { - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: await Appointment.find({ doctorID: userId, startDate: { $gte: new Date() } }), - }; + appointments = await Appointment.find({ doctorID: userId, startDate: { $gte: new Date() } }); } - } else if (role === 'patient' || role === 'Patient') { + } else if (role.toLowerCase() === 'patient') { if (await Patient.exists({ _id: userId })) { - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: await Appointment.find({ patientID: userId, startDate: { $gte: new Date() } }), - }; + appointments = await Appointment.find({ patientID: userId, startDate: { $gte: new Date() } }); } - } - else { + } else { throw new HttpError( StatusCodes.BAD_REQUEST, 'Role is neither a doctor nor a patient or wrong user id' ); } + + if (!appointments || appointments.length === 0) { + throw new HttpError(StatusCodes.NOT_FOUND, 'No upcoming appointments'); + } + + // Fetch doctors and patients based on the extracted ids + const doctorIds = appointments.map(appointment => appointment.doctorID); + const patientIds = appointments.map(appointment => appointment.patientID); + + const doctors = await Doctor.find({ _id: { $in: doctorIds } }); + const patients = await Patient.find({ _id: { $in: patientIds } }); + + const doctorMap = new Map(doctors.map(doctor => [doctor._id.toString(), doctor])); + const patientMap = new Map(patients.map(patient => [patient._id.toString(), patient])); + + const formattedAppointments = appointments.map(appointment => { + const doctor = doctorMap.get(appointment.doctorID.toString()); + const patient = patientMap.get(appointment.patientID.toString()); + + // Accessing the properties directly without _doc + return { + patientName: patient ? patient.get('name') : 'Unknown Patient', + doctorName: doctor ? doctor.get('name') : 'Unknown Doctor', + ...appointment.toObject(), + }; + }); + + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: formattedAppointments, + }; }; + // Function to get past appointments for a user (patient or doctor) contiue req 45 const getPastAppointments = async (userId: string, role: string) => { - if (role === 'doctor' || role === 'Doctor') { + let appointments; + + if (role.toLowerCase() === 'doctor') { if (await Doctor.exists({ _id: userId })) { - return { - status: StatusCodes.OK, - message: 'Past appointments retrieved successfully', - result: await Appointment.find({ doctorID: userId, endDate: { $lt: new Date() } }), - }; + appointments = await Appointment.find({ doctorID: userId, endDate: { $lt: new Date() } }); } - } - else if (role === 'patient' || role === 'Patient') { + } else if (role.toLowerCase() === 'patient') { if (await Patient.exists({ _id: userId })) { - return { - status: StatusCodes.OK, - message: 'Past appointments retrieved successfully', - result: await Appointment.find({ patientID: userId, endDate: { $lt: new Date() } }), - }; + appointments = await Appointment.find({ patientID: userId, endDate: { $lt: new Date() } }); } - } - else { + } else { throw new HttpError( StatusCodes.BAD_REQUEST, 'Role is neither a doctor nor a patient or wrong user id' ); } + + if (!appointments || appointments.length === 0) { + throw new HttpError(StatusCodes.NOT_FOUND, 'No past appointments found'); + } + + // Fetch doctors and patients based on the extracted ids + const doctorIds = appointments.map(appointment => appointment.doctorID); + const patientIds = appointments.map(appointment => appointment.patientID); + + const doctors = await Doctor.find({ _id: { $in: doctorIds } }); + const patients = await Patient.find({ _id: { $in: patientIds } }); + + const doctorMap = new Map(doctors.map(doctor => [doctor._id.toString(), doctor])); + const patientMap = new Map(patients.map(patient => [patient._id.toString(), patient])); + + const formattedAppointments = appointments.map(appointment => { + const doctor = doctorMap.get(appointment.doctorID.toString()); + const patient = patientMap.get(appointment.patientID.toString()); + + // Accessing the properties directly without _doc + return { + patientName: patient ? patient.get('name') : 'Unknown Patient', + doctorName: doctor ? doctor.get('name') : 'Unknown Doctor', + ...appointment.toObject(), + }; + }); + + return { + status: StatusCodes.OK, + message: 'Past appointments retrieved successfully', + result: formattedAppointments, + }; }; + // Function to filter appointments by date or status (upcoming, completed, cancelled, rescheduled) req 46 const filterAppointments = async (query: any) => { const startDate = query.startDate ? new Date(query.startDate) : new Date('1000-01-01T00:00:00.000Z'); const endDate = query.endDate ? new Date(query.endDate) : new Date('9999-01-01T00:00:00.000Z'); const status = query.status ? query.status : { $in: ['Upcoming', 'Completed', 'Cancelled', 'Rescheduled'] }; - if (query.role === 'doctor' || query.role === 'Doctor') { - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: await Appointment.find({ doctorID: query.doctorID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }), - }; - } else if (query.role === 'patient' || query.role === 'Patient') { - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: await Appointment.find({ patientID: query.patientID, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }), - }; + + let appointments; + + if (query.role.toLowerCase() === 'doctor') { + appointments = await Appointment.find({ + doctorID: query.doctorID, + startDate: { $gte: startDate }, + endDate: { $lte: endDate }, + status: status, + }); + } else if (query.role.toLowerCase() === 'patient') { + appointments = await Appointment.find({ + patientID: query.patientID, + startDate: { $gte: startDate }, + endDate: { $lte: endDate }, + status: status, + }); } else { - throw new HttpError( - StatusCodes.BAD_REQUEST, - 'User is neither a doctor nor a patient' - ); + throw new HttpError(StatusCodes.BAD_REQUEST, 'User is neither a doctor nor a patient'); } + + if (!appointments || appointments.length === 0) { + throw new HttpError(StatusCodes.NOT_FOUND, 'No matching appointments found'); + } + + // Fetch doctors and patients based on the extracted ids + const doctorIds = appointments.map(appointment => appointment.doctorID); + const patientIds = appointments.map(appointment => appointment.patientID); + + const doctors = await Doctor.find({ _id: { $in: doctorIds } }); + const patients = await Patient.find({ _id: { $in: patientIds } }); + + const doctorMap = new Map(doctors.map(doctor => [doctor._id.toString(), doctor])); + const patientMap = new Map(patients.map(patient => [patient._id.toString(), patient])); + + const formattedAppointments = appointments.map(appointment => { + const doctor = doctorMap.get(appointment.doctorID.toString()); + const patient = patientMap.get(appointment.patientID.toString()); + + // Accessing the properties directly without _doc + return { + patientName: patient ? patient.get('name') : 'Unknown Patient', + doctorName: doctor ? doctor.get('name') : 'Unknown Doctor', + ...appointment.toObject(), + }; + }); + + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: formattedAppointments, + }; }; + export { getAppointments, createAppointment , getUpcomingAppointments, getPastAppointments, filterAppointments }; From 8856737b473c9ecc04713548dd6d95451429b593 Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sat, 11 Nov 2023 20:30:00 +0200 Subject: [PATCH 06/15] if there is no appointments it will return empty array instead of throwing error --- client/src/sections/user/view/user-view.jsx | 14 ++++++------ server/src/services/appointment.service.ts | 24 +++++++++++++++++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/client/src/sections/user/view/user-view.jsx b/client/src/sections/user/view/user-view.jsx index 49b7323..7589a19 100644 --- a/client/src/sections/user/view/user-view.jsx +++ b/client/src/sections/user/view/user-view.jsx @@ -116,7 +116,7 @@ export default function UserPage() {
handleClick(event, row.doctorID)} + selected={selected.indexOf(row._id) !== -1} + handleClick={(event) => handleClick(event, row._id)} /> ))} @@ -202,4 +202,4 @@ export default function UserPage() { ); -} +} \ No newline at end of file diff --git a/server/src/services/appointment.service.ts b/server/src/services/appointment.service.ts index ffc995f..8e0a478 100644 --- a/server/src/services/appointment.service.ts +++ b/server/src/services/appointment.service.ts @@ -10,7 +10,11 @@ const getAppointments = async (query: any) => { const appointments = await Appointment.find(query); if (!appointments || appointments.length === 0) { - throw new HttpError(StatusCodes.NOT_FOUND, 'No upcoming appointments'); + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: [], + }; } const doctorIds = appointments.map(appointment => appointment.doctorID); @@ -78,7 +82,11 @@ const getUpcomingAppointments = async (userId: string, role: string) => { } if (!appointments || appointments.length === 0) { - throw new HttpError(StatusCodes.NOT_FOUND, 'No upcoming appointments'); + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: [], + }; } // Fetch doctors and patients based on the extracted ids @@ -132,7 +140,11 @@ const getPastAppointments = async (userId: string, role: string) => { } if (!appointments || appointments.length === 0) { - throw new HttpError(StatusCodes.NOT_FOUND, 'No past appointments found'); + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: [], + }; } // Fetch doctors and patients based on the extracted ids @@ -193,7 +205,11 @@ const filterAppointments = async (query: any) => { } if (!appointments || appointments.length === 0) { - throw new HttpError(StatusCodes.NOT_FOUND, 'No matching appointments found'); + return { + status: StatusCodes.OK, + message: 'Appointments retrieved successfully', + result: [], + }; } // Fetch doctors and patients based on the extracted ids From eb2208b9bfe4b2ddadf999508e9ae565b95f4f3c Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sat, 11 Nov 2023 20:35:15 +0200 Subject: [PATCH 07/15] added a message if no appointments for the user --- client/src/sections/user/view/user-view.jsx | 122 +++++++++++--------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/client/src/sections/user/view/user-view.jsx b/client/src/sections/user/view/user-view.jsx index 7589a19..9ca1e8d 100644 --- a/client/src/sections/user/view/user-view.jsx +++ b/client/src/sections/user/view/user-view.jsx @@ -141,64 +141,72 @@ export default function UserPage() { - - -
- - - {appointments - .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row) => ( - handleClick(event, row._id)} + {appointments.length === 0 ? ( + + No appointments found. + + ) : ( + <> + + +
+ + + {appointments + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row) => ( + handleClick(event, row._id)} + /> + ))} + + - ))} - - - -
-
-
- - + + + +
+ + + + )}
); From bad1c2a61b5a3d78743dc5cd19908d82c7a1139b Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sat, 11 Nov 2023 21:50:47 +0200 Subject: [PATCH 08/15] filter option --- client/src/sections/user/view/user-view.jsx | 222 +++++++++++++------- 1 file changed, 149 insertions(+), 73 deletions(-) diff --git a/client/src/sections/user/view/user-view.jsx b/client/src/sections/user/view/user-view.jsx index 9ca1e8d..0223674 100644 --- a/client/src/sections/user/view/user-view.jsx +++ b/client/src/sections/user/view/user-view.jsx @@ -16,19 +16,39 @@ import { emptyRows, getComparator } from '../utils'; import { axiosInstance } from '../../../utils/axiosInstance'; import Iconify from 'src/components/iconify'; import Scrollbar from 'src/components/scrollbar'; +import TextField from '@mui/material/TextField'; +import Autocomplete from '@mui/material/Autocomplete'; +import Popover from '@mui/material/Popover'; +import Box from '@mui/material/Box'; export default function UserPage() { const [appointments, setAppointments] = useState([]); const [page, setPage] = useState(0); const [order, setOrder] = useState('asc'); const [selected, setSelected] = useState([]); - const [orderBy, setOrderBy] = useState('doctorID'); // Set default ordering based on doctorID + const [orderBy, setOrderBy] = useState('doctorID'); const [rowsPerPage, setRowsPerPage] = useState(5); + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + const [status, setStatus] = useState(''); + + const [anchorEl, setAnchorEl] = useState(null); + useEffect(() => { const fetchAppointments = async () => { try { - const response = await axiosInstance.get('/me/appointments'); + let endpoint = '/me/appointments'; + + if (startDate || endDate || status) { + endpoint += '?s=filter'; + + if (startDate) endpoint += `&startDate=${new Date(startDate).toISOString()}`; + if (endDate) endpoint += `&endDate=${new Date(endDate).toISOString()}`; + if (status) endpoint += `&status=${status}`; + } + + const response = await axiosInstance.get(endpoint); setAppointments(response.data.result); } catch (error) { console.error('Error fetching appointments:', error); @@ -36,14 +56,12 @@ export default function UserPage() { }; fetchAppointments(); - }, []); // Fetch appointments on component mount + }, [startDate, endDate, status]); const handleSort = (event, id) => { const isAsc = orderBy === id && order === 'asc'; - if (id !== '') { - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(id); - } + setOrder(isAsc ? 'desc' : 'asc'); + setOrderBy(id); }; const handleSelectAllClick = (event) => { @@ -108,6 +126,20 @@ export default function UserPage() { console.error('Error fetching all appointments:', error); } }; + + const handleFilterButtonClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleFilterClose = () => { + setAnchorEl(null); + }; + + const handleApplyFilters = () => { + fetchAppointments(); + handleFilterClose(); + }; + return ( @@ -130,6 +162,7 @@ export default function UserPage() { > Get Past Appointments + + + + + + + setStartDate(e.target.value)} + /> + setEndDate(e.target.value)} + /> + ( + + )} + onChange={(e, value) => setStatus(value)} + /> + + + - {appointments.length === 0 ? ( - - No appointments found. - - ) : ( - <> - - - - - - {appointments - .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row) => ( - handleClick(event, row._id)} - /> - ))} - - + +
+ + + {appointments + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row) => ( + handleClick(event, row._id)} /> - -
-
-
- - - - )} + ))} + + + + + + + +
); -} \ No newline at end of file +} From 645e3f3f05cf1642368f02d119e9b98c3da6c238 Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sat, 11 Nov 2023 22:16:34 +0200 Subject: [PATCH 09/15] filtering working --- client/src/sections/user/view/user-view.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/sections/user/view/user-view.jsx b/client/src/sections/user/view/user-view.jsx index 0223674..2214304 100644 --- a/client/src/sections/user/view/user-view.jsx +++ b/client/src/sections/user/view/user-view.jsx @@ -29,6 +29,7 @@ export default function UserPage() { const [orderBy, setOrderBy] = useState('doctorID'); const [rowsPerPage, setRowsPerPage] = useState(5); + // State for filter values const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); const [status, setStatus] = useState(''); @@ -198,6 +199,7 @@ export default function UserPage() { InputLabelProps={{ shrink: true, }} + value={startDate || ''} onChange={(e) => setStartDate(e.target.value)} /> setEndDate(e.target.value)} /> ( )} + value={status} onChange={(e, value) => setStatus(value)} /> + )} setEndDate(e.target.value)} + onChange={(e) => + setFilterValues({ ...filterValues, endDate: e.target.value }) + } + value={filterValues.endDate || ''} /> + {filterValues.endDate && ( + + )} ( )} - value={status} - onChange={(e, value) => setStatus(value)} + onChange={(e, value) => + setFilterValues({ ...filterValues, status: value }) + } + value={filterValues.status || ''} /> + From 9d11d5f023c269e082e0930da86e83044805923f Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sun, 12 Nov 2023 16:44:31 +0200 Subject: [PATCH 12/15] putting getwallet after pulling --- server/src/services/doctor.service.ts | 28 +++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/server/src/services/doctor.service.ts b/server/src/services/doctor.service.ts index fd22c42..829d1b0 100644 --- a/server/src/services/doctor.service.ts +++ b/server/src/services/doctor.service.ts @@ -1,7 +1,7 @@ const { v4: uuidv4 } = require('uuid'); import { HttpError } from '../utils'; import StatusCodes from 'http-status-codes'; -import { User, Contract, Appointment, IPatient, IDoctor, Doctor,Request } from '../models'; +import { User, Contract, Appointment, IPatient, IDoctor, Doctor,Request, Patient } from '../models'; const getMyPatients = async (query: any) => { const appointments = await Appointment.find(query).distinct('patientID').select('patientID').populate('patientID'); @@ -178,5 +178,29 @@ const viewAvailableAppointments = async (doctorID: string) => { result: availableAppointments }; }; +// View the amount in my wallet req 67 for patient and doctor +const viewWallet = async (userId: string, role: string) => { + let user = null; + let userType = ""; + + if (role === 'patient' || role === 'Patient') { + user = await Patient.findById(userId); + userType = 'Patients'; + } else if (role === 'doctor' || role === 'Doctor') { + user = await Doctor.findById(userId); + userType = 'Doctor'; + } + if (!user) { + throw new HttpError( + StatusCodes.NOT_FOUND, + `${userType} not found` + ); + } -export { getDoctors, getMyPatients, viewAvailableAppointments, saveRegistrationFiles }; + return { + result: user.wallet, + status: StatusCodes.OK, + message: `Successfully retrieved ${userType}'s wallet` + }; +}; +export { getDoctors, getMyPatients, viewAvailableAppointments, saveRegistrationFiles , viewWallet}; From bec8e1cc876311eed1ac6b480307e51cf52d8c30 Mon Sep 17 00:00:00 2001 From: Zeyad Hesham Date: Sun, 12 Nov 2023 17:22:30 +0200 Subject: [PATCH 13/15] modifying the appointments to get name with refactoring --- server/src/routes/me.route.ts | 7 +- server/src/services/appointment.service.ts | 266 +++++++-------------- 2 files changed, 88 insertions(+), 185 deletions(-) diff --git a/server/src/routes/me.route.ts b/server/src/routes/me.route.ts index 40463f2..c944a5a 100644 --- a/server/src/routes/me.route.ts +++ b/server/src/routes/me.route.ts @@ -10,9 +10,8 @@ import { addFamilyMember, getFamily, getHealthRecords, - getPastAppointments, filterAppointments, - getUpcomingAppointments, + getUpcoming_Past_Appointments, viewWallet } from '../services'; @@ -33,10 +32,10 @@ router.use(isAuthorized('Doctor', 'Patient')); // get my appointments router.get('/appointments', (req: Request, res: Response) => { if (req.query.s === 'Upcoming') { - controller(res)(getUpcomingAppointments)(req.decoded.id, req.decoded.role); + controller(res)(getUpcoming_Past_Appointments)(req.decoded.id, req.decoded.role,"Upcoming"); } else if (req.query.s === 'Completed') { - controller(res)(getPastAppointments)(req.decoded.id, req.decoded.role) + controller(res)(getUpcoming_Past_Appointments)(req.decoded.id, req.decoded.role, "Completed"); } else if (req.query.s === 'filter') { // user's id field depends on the role diff --git a/server/src/services/appointment.service.ts b/server/src/services/appointment.service.ts index 8e0a478..0a679f8 100644 --- a/server/src/services/appointment.service.ts +++ b/server/src/services/appointment.service.ts @@ -1,48 +1,34 @@ -import { Appointment, Doctor , Patient} from '../models'; +import { Appointment, Doctor, Patient } from '../models'; import { StatusCodes } from 'http-status-codes'; import mongoose from 'mongoose'; import { HttpError } from '../utils'; const getAppointments = async (query: any) => { + // Update the query based on startDate and endDate if provided if (query.startDate) query.startDate = { $gte: query.startDate }; if (query.endDate) query.endDate = { $lte: query.endDate }; - const appointments = await Appointment.find(query); - - if (!appointments || appointments.length === 0) { - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: [], - }; - } - - const doctorIds = appointments.map(appointment => appointment.doctorID); - const patientIds = appointments.map(appointment => appointment.patientID); - - // Fetch doctors and patients based on the extracted ids - const doctors = await Doctor.find({ _id: { $in: doctorIds } }); - const patients = await Patient.find({ _id: { $in: patientIds } }); - - const doctorMap = new Map(doctors.map(doctor => [doctor._id.toString(), doctor])); - const patientMap = new Map(patients.map(patient => [patient._id.toString(), patient])); - - const formattedAppointments = appointments.map(appointment => { - const doctor = doctorMap.get(appointment.doctorID.toString()); - const patient = patientMap.get(appointment.patientID.toString()); - - // Accessing the properties directly without _doc - return { - patientName: patient ? patient.get('name') : 'Unknown Patient', - doctorName: doctor ? doctor.get('name') : 'Unknown Doctor', - ...appointment.toObject(), - }; - }); - + // Fetch appointments with populated doctor and patient names + const appointments = await Appointment.find(query) + .populate('doctorID', 'name') // Assuming 'name' is the field in the Doctor model + .populate('patientID', 'name'); // Assuming 'name' is the field in the Patient model + + // Transform the result to the desired format + const formattedAppointments = appointments.map(appointment => ({ + patientName: (appointment.patientID as any).name, + doctorName: (appointment.doctorID as any).name, + _id: appointment._id, + status: appointment.status, + sessionPrice: appointment.sessionPrice, + startDate: appointment.startDate, + endDate: appointment.endDate, + isFollowUp: appointment.isFollowUp, + __v: appointment.__v, + })); return { status: StatusCodes.OK, message: 'Appointments retrieved successfully', - result: formattedAppointments, + result: formattedAppointments || [] // Return an empty array if appointments is falsy }; }; @@ -62,76 +48,23 @@ const createAppointment = async (patientID: String, doctorID: String, body: any) }; }; -// Function to get upcoming appointments for a user (patient or doctor) req 45 -const getUpcomingAppointments = async (userId: string, role: string) => { - let appointments; - - if (role.toLowerCase() === 'doctor') { - if (await Doctor.exists({ _id: userId })) { - appointments = await Appointment.find({ doctorID: userId, startDate: { $gte: new Date() } }); - } - } else if (role.toLowerCase() === 'patient') { - if (await Patient.exists({ _id: userId })) { - appointments = await Appointment.find({ patientID: userId, startDate: { $gte: new Date() } }); - } - } else { - throw new HttpError( - StatusCodes.BAD_REQUEST, - 'Role is neither a doctor nor a patient or wrong user id' - ); - } +// Function to get upcoming or past appointments for a user (patient or doctor) req 45 +const getUpcoming_Past_Appointments = async (userId: string, role: string, status: string) => { + let query: any = {}; + let model: any; - if (!appointments || appointments.length === 0) { - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: [], + if ((role === 'doctor' || role === 'Doctor') && (await Doctor.exists({ _id: userId }))) { + query = { + doctorID: userId, + startDate: status === 'Upcoming' ? { $gte: new Date() } : { $lt: new Date() }, }; - } - - // Fetch doctors and patients based on the extracted ids - const doctorIds = appointments.map(appointment => appointment.doctorID); - const patientIds = appointments.map(appointment => appointment.patientID); - - const doctors = await Doctor.find({ _id: { $in: doctorIds } }); - const patients = await Patient.find({ _id: { $in: patientIds } }); - - const doctorMap = new Map(doctors.map(doctor => [doctor._id.toString(), doctor])); - const patientMap = new Map(patients.map(patient => [patient._id.toString(), patient])); - - const formattedAppointments = appointments.map(appointment => { - const doctor = doctorMap.get(appointment.doctorID.toString()); - const patient = patientMap.get(appointment.patientID.toString()); - - // Accessing the properties directly without _doc - return { - patientName: patient ? patient.get('name') : 'Unknown Patient', - doctorName: doctor ? doctor.get('name') : 'Unknown Doctor', - ...appointment.toObject(), + model = Doctor; + } else if ((role === 'patient' || role === 'Patient') && (await Patient.exists({ _id: userId }))) { + query = { + patientID: userId, + startDate: status === 'Upcoming' ? { $gte: new Date() } : { $lt: new Date() }, }; - }); - - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: formattedAppointments, - }; -}; - - - -// Function to get past appointments for a user (patient or doctor) contiue req 45 -const getPastAppointments = async (userId: string, role: string) => { - let appointments; - - if (role.toLowerCase() === 'doctor') { - if (await Doctor.exists({ _id: userId })) { - appointments = await Appointment.find({ doctorID: userId, endDate: { $lt: new Date() } }); - } - } else if (role.toLowerCase() === 'patient') { - if (await Patient.exists({ _id: userId })) { - appointments = await Appointment.find({ patientID: userId, endDate: { $lt: new Date() } }); - } + model = Patient; } else { throw new HttpError( StatusCodes.BAD_REQUEST, @@ -139,107 +72,78 @@ const getPastAppointments = async (userId: string, role: string) => { ); } - if (!appointments || appointments.length === 0) { + const appointments = await Appointment.find(query) + .populate('patientID', 'name') + .populate('doctorID', 'name'); + + if (model) { + const formattedAppointments = appointments.map(appointment => ({ + patientName: (appointment.patientID as any)?.name, + doctorName: (appointment.doctorID as any)?.name, + _id: appointment._id, + status: appointment.status, + sessionPrice: appointment.sessionPrice, + startDate: appointment.startDate, + endDate: appointment.endDate, + isFollowUp: appointment.isFollowUp, + __v: appointment.__v, + })); + return { status: StatusCodes.OK, message: 'Appointments retrieved successfully', - result: [], + result: formattedAppointments, }; } - - // Fetch doctors and patients based on the extracted ids - const doctorIds = appointments.map(appointment => appointment.doctorID); - const patientIds = appointments.map(appointment => appointment.patientID); - - const doctors = await Doctor.find({ _id: { $in: doctorIds } }); - const patients = await Patient.find({ _id: { $in: patientIds } }); - - const doctorMap = new Map(doctors.map(doctor => [doctor._id.toString(), doctor])); - const patientMap = new Map(patients.map(patient => [patient._id.toString(), patient])); - - const formattedAppointments = appointments.map(appointment => { - const doctor = doctorMap.get(appointment.doctorID.toString()); - const patient = patientMap.get(appointment.patientID.toString()); - - // Accessing the properties directly without _doc - return { - patientName: patient ? patient.get('name') : 'Unknown Patient', - doctorName: doctor ? doctor.get('name') : 'Unknown Doctor', - ...appointment.toObject(), - }; - }); - - return { - status: StatusCodes.OK, - message: 'Past appointments retrieved successfully', - result: formattedAppointments, - }; }; - - // Function to filter appointments by date or status (upcoming, completed, cancelled, rescheduled) req 46 const filterAppointments = async (query: any) => { const startDate = query.startDate ? new Date(query.startDate) : new Date('1000-01-01T00:00:00.000Z'); const endDate = query.endDate ? new Date(query.endDate) : new Date('9999-01-01T00:00:00.000Z'); const status = query.status ? query.status : { $in: ['Upcoming', 'Completed', 'Cancelled', 'Rescheduled'] }; - let appointments; - - if (query.role.toLowerCase() === 'doctor') { - appointments = await Appointment.find({ - doctorID: query.doctorID, - startDate: { $gte: startDate }, - endDate: { $lte: endDate }, - status: status, - }); - } else if (query.role.toLowerCase() === 'patient') { - appointments = await Appointment.find({ - patientID: query.patientID, - startDate: { $gte: startDate }, - endDate: { $lte: endDate }, - status: status, - }); + let model: any; + let roleQuery: any; + + if ((query.role === 'doctor' || query.role === 'Doctor') && (await Doctor.exists({ _id: query.doctorID }))) { + model = Doctor; + roleQuery = { doctorID: query.doctorID }; + } else if ((query.role === 'patient' || query.role === 'Patient') && (await Patient.exists({ _id: query.patientID }))) { + model = Patient; + roleQuery = { patientID: query.patientID }; } else { - throw new HttpError(StatusCodes.BAD_REQUEST, 'User is neither a doctor nor a patient'); + throw new HttpError( + StatusCodes.BAD_REQUEST, + 'User is neither a doctor nor a patient' + ); } - if (!appointments || appointments.length === 0) { + const appointments = await Appointment.find({ ...roleQuery, startDate: { $gte: startDate }, endDate: { $lte: endDate }, status: status }) + .populate('patientID', 'name') // Assuming 'name' is the field in the Patient model + .populate('doctorID', 'name'); // Assuming 'name' is the field in the Doctor model + + if (model) { + const formattedAppointments = appointments.map(appointment => ({ + patientName: (appointment.patientID as any)?.name , + doctorName: (appointment.doctorID as any)?.name , + _id: appointment._id, + status: appointment.status, + sessionPrice: appointment.sessionPrice, + startDate: appointment.startDate, + endDate: appointment.endDate, + isFollowUp: appointment.isFollowUp, + __v: appointment.__v, + })); + return { status: StatusCodes.OK, message: 'Appointments retrieved successfully', - result: [], + result: formattedAppointments, }; } - - // Fetch doctors and patients based on the extracted ids - const doctorIds = appointments.map(appointment => appointment.doctorID); - const patientIds = appointments.map(appointment => appointment.patientID); - - const doctors = await Doctor.find({ _id: { $in: doctorIds } }); - const patients = await Patient.find({ _id: { $in: patientIds } }); - - const doctorMap = new Map(doctors.map(doctor => [doctor._id.toString(), doctor])); - const patientMap = new Map(patients.map(patient => [patient._id.toString(), patient])); - - const formattedAppointments = appointments.map(appointment => { - const doctor = doctorMap.get(appointment.doctorID.toString()); - const patient = patientMap.get(appointment.patientID.toString()); - - // Accessing the properties directly without _doc - return { - patientName: patient ? patient.get('name') : 'Unknown Patient', - doctorName: doctor ? doctor.get('name') : 'Unknown Doctor', - ...appointment.toObject(), - }; - }); - - return { - status: StatusCodes.OK, - message: 'Appointments retrieved successfully', - result: formattedAppointments, - }; }; -export { getAppointments, createAppointment , getUpcomingAppointments, getPastAppointments, filterAppointments }; + +export { getAppointments, createAppointment, getUpcoming_Past_Appointments, filterAppointments }; From 6f4bf989d104d763fbe3937793fbf63b00b01c5b Mon Sep 17 00:00:00 2001 From: Mark Mahrous Date: Tue, 14 Nov 2023 13:48:36 +0200 Subject: [PATCH 14/15] not showing any medical record that is file uploaded in health records --- server/src/services/patient.service.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/src/services/patient.service.ts b/server/src/services/patient.service.ts index e589474..dd9b63e 100644 --- a/server/src/services/patient.service.ts +++ b/server/src/services/patient.service.ts @@ -210,13 +210,23 @@ const getHealthRecords = async (patientID: String) => { const healthRecords = user.medicalHistory; if (!healthRecords) throw new HttpError(StatusCodes.NOT_FOUND, 'health records not found'); + //create a new array of objects where the medicalRecord is not a URL + let filteredRecords = healthRecords.filter((record: any) => isNotURL(record.medicalRecord)); + if (!filteredRecords) throw new HttpError(StatusCodes.NOT_FOUND, 'health records not found'); + // console.log(healthRecords); + // console.log(filteredRecords); return { - result: healthRecords, + result: filteredRecords, status: StatusCodes.OK, message: 'Health records retrieved successfully' }; }; + +const isNotURL = (str: String) => { + return !str.endsWith('.pdf') && !str.endsWith('.jpeg') && !str.endsWith('.jpg') && !str.endsWith('.png'); +}; + export { viewDoctorsForPatient as viewAllDoctorsForPatient, getFamily, From d833949d7f7f48239f8b29bd1921bda2d96acec8 Mon Sep 17 00:00:00 2001 From: Mark Mahrous Date: Tue, 14 Nov 2023 14:41:58 +0200 Subject: [PATCH 15/15] adding option for patient to view his own health records --- client/src/layouts/dashboard/config-navigation.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/layouts/dashboard/config-navigation.jsx b/client/src/layouts/dashboard/config-navigation.jsx index 1c990b4..dac7ae2 100644 --- a/client/src/layouts/dashboard/config-navigation.jsx +++ b/client/src/layouts/dashboard/config-navigation.jsx @@ -47,6 +47,11 @@ const navConfig = [ path: '/patients', icon: icon('ic_user'), }, + { + title: 'My Health Records', + path: '/health-record', + icon: icon('ic_analytics'), + }, { title: 'Not found', path: '/404',