From 45b075c98969b088cade53f56481514f3a57795f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 9 Apr 2026 05:05:55 +0000
Subject: [PATCH 1/5] Initial plan
From b3b3442422f3482389d64792f931e9e335e9ef39 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 9 Apr 2026 05:14:30 +0000
Subject: [PATCH 2/5] Implement MediChain V2: episodes backend, trust score UI,
episode doctor/patient flows
Agent-Logs-Url: https://github.com/CodeVoyager3/MediChain/sessions/81c09f02-e413-46eb-88d5-2e4f6734c8a0
Co-authored-by: mayank1008-tech <245725096+mayank1008-tech@users.noreply.github.com>
---
Frontend/package-lock.json | 116 ++++++++++--------
.../components/dashboard/DoctorDashboard.jsx | 73 ++++++++++-
.../components/dashboard/InsurerDashboard.jsx | 51 ++++++++
.../components/dashboard/PatientDashboard.jsx | 90 +++++++++++++-
Frontend/src/services/api.js | 8 +-
backend/mvnw | 0
.../controller/BlockchainController.java | 5 +-
.../controller/DashboardController.java | 65 ++++++++--
.../backend/dto/CreateEpisodeRequest.java | 10 ++
.../backend/dto/MintRecordRequest.java | 1 +
.../org/medichain/backend/entity/Episode.java | 32 +++++
.../backend/entity/MedicalRecord.java | 3 +
.../backend/repository/EpisodeRepository.java | 12 ++
.../backend/service/BlockchainService.java | 3 +-
.../backend/service/EpisodeService.java | 75 +++++++++++
15 files changed, 470 insertions(+), 74 deletions(-)
mode change 100644 => 100755 backend/mvnw
create mode 100644 backend/src/main/java/org/medichain/backend/dto/CreateEpisodeRequest.java
create mode 100644 backend/src/main/java/org/medichain/backend/entity/Episode.java
create mode 100644 backend/src/main/java/org/medichain/backend/repository/EpisodeRepository.java
create mode 100644 backend/src/main/java/org/medichain/backend/service/EpisodeService.java
diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json
index 5a2c8c6..564a45e 100644
--- a/Frontend/package-lock.json
+++ b/Frontend/package-lock.json
@@ -94,7 +94,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -735,7 +734,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -947,6 +945,31 @@
"@noble/ciphers": "^1.0.0"
}
},
+ "node_modules/@emnapi/core": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
+ "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+ "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
@@ -954,6 +977,7 @@
"dev": true,
"license": "MIT",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -1031,7 +1055,6 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -1397,6 +1420,7 @@
"resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.7.4.tgz",
"integrity": "sha512-DGd9yeSQzflOWO3Y5mt1GRXkXH9O/yIMgbxPjwLI3jwu/3nAjoXXD26lEeFb6tclYlg0JAqTIs5d930G/qxHeA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@hey-api/types": "0.1.4",
"ansi-colors": "4.1.3",
@@ -1415,6 +1439,7 @@
"resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.3.1.tgz",
"integrity": "sha512-7atnpUkT8TyUPHYPLk91j/GyaqMuwTEHanLOe50Dlx0EEvNuQqFD52Yjg8x4KU0UFL1mWlyhE+sUE/wAtQ1N2A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jsdevtools/ono": "7.1.3",
"@types/json-schema": "7.0.15",
@@ -1462,6 +1487,7 @@
"resolved": "https://registry.npmjs.org/@hey-api/shared/-/shared-0.3.0.tgz",
"integrity": "sha512-G+4GPojdLEh9bUwRG88teMPM1HdqMm/IsJ38cbnNxhyDu1FkFGwilkA1EqnULCzfTam/ZoZkaLdmAd8xEh4Xsw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@hey-api/codegen-core": "0.7.4",
"@hey-api/json-schema-ref-parser": "1.3.1",
@@ -1484,6 +1510,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
+ "peer": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -1496,6 +1523,7 @@
"resolved": "https://registry.npmjs.org/@hey-api/spec-types/-/spec-types-0.1.0.tgz",
"integrity": "sha512-StS4RrAO5pyJCBwe6uF9MAuPflkztriW+FPnVb7oEjzDYv1sxPwP+f7fL6u6D+UVrKpZ/9bPNx/xXVdkeWPU6A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@hey-api/types": "0.1.4"
},
@@ -1507,7 +1535,8 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@hey-api/types/-/types-0.1.4.tgz",
"integrity": "sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@hono/node-server": {
"version": "1.19.12",
@@ -1712,7 +1741,8 @@
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@kurkle/color": {
"version": "0.3.4",
@@ -2244,7 +2274,6 @@
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
"integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"node-fetch": "^2.7.0"
}
@@ -2476,7 +2505,6 @@
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": "^14.21.3 || >=16"
},
@@ -3414,7 +3442,6 @@
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz",
"integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.17.8",
"@types/webxr": "*",
@@ -3773,7 +3800,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -4129,7 +4155,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -4437,7 +4462,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -5161,7 +5185,6 @@
"resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.5.1.tgz",
"integrity": "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@solana/accounts": "5.5.1",
"@solana/addresses": "5.5.1",
@@ -5783,7 +5806,6 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.81.5.tgz",
"integrity": "sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@tanstack/query-core": "5.81.5"
},
@@ -5957,7 +5979,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -5968,7 +5989,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -6000,7 +6020,6 @@
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz",
"integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@dimforge/rapier3d-compat": "~0.12.0",
"@tweenjs/tween.js": "~23.1.3",
@@ -6204,7 +6223,6 @@
"resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.22.1.tgz",
"integrity": "sha512-cG/xwQWsBEcKgRTkQVhH29cbpbs/TdcUJVFXCyri3ZknxhMyGv0YEjTcrNpRgt2SaswL1KrvslSNYKKo+5YEAg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"eventemitter3": "5.0.1",
"mipd": "0.0.7",
@@ -6589,7 +6607,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -7094,7 +7111,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7186,6 +7202,7 @@
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6"
}
@@ -7368,7 +7385,6 @@
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
@@ -7604,7 +7620,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -7658,7 +7673,6 @@
"integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==",
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
@@ -7696,6 +7710,7 @@
"resolved": "https://registry.npmjs.org/c12/-/c12-3.3.3.tgz",
"integrity": "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"chokidar": "^5.0.0",
"confbox": "^0.2.2",
@@ -7724,6 +7739,7 @@
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"readdirp": "^5.0.0"
},
@@ -7739,6 +7755,7 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"license": "MIT",
+ "peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -7748,6 +7765,7 @@
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">= 20.19.0"
},
@@ -7923,7 +7941,6 @@
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -7974,6 +7991,7 @@
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"consola": "^3.2.3"
}
@@ -8144,6 +8162,7 @@
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"license": "ISC",
+ "peer": true,
"bin": {
"color-support": "bin.js"
}
@@ -8180,13 +8199,15 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/consola": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
@@ -8718,7 +8739,6 @@
"resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.18.tgz",
"integrity": "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@ecies/ciphers": "^0.2.5",
"@noble/ciphers": "^1.3.0",
@@ -8928,7 +8948,6 @@
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -9307,8 +9326,7 @@
"version": "6.4.9",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz",
"integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/eventemitter3": {
"version": "5.0.1",
@@ -9452,7 +9470,8 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/extension-port-stream": {
"version": "3.0.0",
@@ -10053,6 +10072,7 @@
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
"integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
@@ -10065,6 +10085,7 @@
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.4.0",
@@ -10266,7 +10287,6 @@
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.10.tgz",
"integrity": "sha512-mx/p18PLy5og9ufies2GOSUqep98Td9q4i/EF6X7yJgAiIopxqdfIO3jbqsi3jRgTgw88jMDEzVKi+V2EF+27w==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=16.9.0"
}
@@ -10815,7 +10835,6 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -11893,6 +11912,7 @@
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz",
"integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"citty": "^0.2.0",
"pathe": "^2.0.3",
@@ -11909,7 +11929,8 @@
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz",
"integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/obj-multiplex": {
"version": "1.0.0",
@@ -12016,7 +12037,8 @@
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/on-exit-leak-free": {
"version": "0.2.0",
@@ -12330,13 +12352,15 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/perfect-debounce": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/picocolors": {
"version": "1.1.1",
@@ -12430,6 +12454,7 @@
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"confbox": "^0.2.2",
"exsolve": "^1.0.7",
@@ -12580,7 +12605,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -12614,7 +12638,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -13183,6 +13206,7 @@
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"defu": "^6.1.4",
"destr": "^2.0.3"
@@ -13193,7 +13217,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -13213,7 +13236,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -13364,7 +13386,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -13484,6 +13505,7 @@
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"license": "MIT",
+ "peer": true,
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
@@ -13975,7 +13997,6 @@
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
@@ -14562,8 +14583,7 @@
"version": "0.183.2",
"resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz",
"integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/three-mesh-bvh": {
"version": "0.8.3",
@@ -14609,6 +14629,7 @@
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
"integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -15198,7 +15219,6 @@
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
@@ -15258,7 +15278,6 @@
"resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz",
"integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"derive-valtio": "0.1.0",
"proxy-compare": "2.6.0",
@@ -15310,7 +15329,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"@noble/curves": "1.9.1",
"@noble/hashes": "1.8.0",
@@ -15423,7 +15441,6 @@
"integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
@@ -15501,7 +15518,6 @@
"resolved": "https://registry.npmjs.org/wagmi/-/wagmi-2.19.5.tgz",
"integrity": "sha512-RQUfKMv6U+EcSNNGiPbdkDtJwtuFxZWLmvDiQmjjBgkuPulUwDJsKhi7gjynzJdsx2yDqhHCXkKsbbfbIsHfcQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@wagmi/connectors": "6.2.0",
"@wagmi/core": "2.22.1",
@@ -15683,7 +15699,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -15983,7 +15998,6 @@
"resolved": "https://registry.npmjs.org/@solana/kit/-/kit-2.3.0.tgz",
"integrity": "sha512-sb6PgwoW2LjE5oTFu4lhlS/cGt/NB3YrShEyx7JgWFWysfgLdJnhwWThgwy/4HjNsmtMrQGWVls0yVBHcMvlMQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@solana/accounts": "2.3.0",
"@solana/addresses": "2.3.0",
@@ -16340,7 +16354,6 @@
"resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-2.3.0.tgz",
"integrity": "sha512-LvjADZrpZ+CnhlHqfI5cmsRzX9Rpyb1Ox2dMHnbsRNzeKAMhu9w4ZBIaeTdO322zsTr509G1B+k2ABD3whvUBA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@solana/accounts": "2.3.0",
"@solana/codecs": "2.3.0",
@@ -16591,7 +16604,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.75.tgz",
"integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/Frontend/src/components/dashboard/DoctorDashboard.jsx b/Frontend/src/components/dashboard/DoctorDashboard.jsx
index 3df6423..d5c3703 100644
--- a/Frontend/src/components/dashboard/DoctorDashboard.jsx
+++ b/Frontend/src/components/dashboard/DoctorDashboard.jsx
@@ -15,7 +15,7 @@ import { motion, AnimatePresence } from 'framer-motion';
import { AmbientParticles } from '../effects/AmbientParticles';
import { GlassCard } from '../effects/GlassCard';
import { useAuth } from '../../context/AuthContext';
-import { getWaitingRoom, getAccessibleRecords, completeAppointment, mintRecord, amendRecord } from '../../services/api';
+import { getWaitingRoom, getAccessibleRecords, completeAppointment, mintRecord, amendRecord, createEpisode } from '../../services/api';
const NAV = {
main: [{ id: 'overview', label: 'Overview', icon: LayoutDashboard }, { id: 'waiting', label: 'Waiting Room', icon: Clock }],
@@ -84,6 +84,14 @@ export default function DoctorDashboard() {
const [amendFile, setAmendFile] = useState(null);
const [isAmending, setIsAmending] = useState(false);
+ // Episode state
+ const [episodes, setEpisodes] = useState([]);
+ const [showEpisodeModal, setShowEpisodeModal] = useState(false);
+ const [episodeTitle, setEpisodeTitle] = useState('');
+ const [episodeDescription, setEpisodeDescription] = useState('');
+ const [isCreatingEpisode, setIsCreatingEpisode] = useState(false);
+ const [selectedEpisodeId, setSelectedEpisodeId] = useState('');
+
const displayName = user?.name || 'Doctor';
const normalizeCid = (value) => {
@@ -167,10 +175,12 @@ export default function DoctorDashboard() {
try {
console.log("Uploading to IPFS via Thirdweb...");
const uri = await upload({ client, files: [fileToMint] });
- await mintRecord(targetAddress, uri, fileToMint.name);
+ const epId = selectedEpisodeId ? parseInt(selectedEpisodeId, 10) : null;
+ await mintRecord(targetAddress, uri, fileToMint.name, null, epId);
setFileToMint(null);
setPatientAddressToMint('');
+ setSelectedEpisodeId('');
if (targetAddress === selectedPatient) {
await handleSelectPatient(targetAddress);
@@ -205,6 +215,25 @@ export default function DoctorDashboard() {
}
};
+ const handleCreateEpisode = async () => {
+ const targetAddress = selectedPatient || patientAddressToMint;
+ if (!episodeTitle.trim() || !targetAddress) { setErrorMsg("Episode title and patient address are required."); return; }
+ setIsCreatingEpisode(true); setErrorMsg('');
+ try {
+ const res = await createEpisode(targetAddress, episodeTitle.trim(), episodeDescription.trim());
+ const ep = res.data;
+ setEpisodes(prev => [ep, ...prev]);
+ setSelectedEpisodeId(String(ep.episodeId));
+ setEpisodeTitle('');
+ setEpisodeDescription('');
+ setShowEpisodeModal(false);
+ } catch (err) {
+ setErrorMsg(err.message);
+ } finally {
+ setIsCreatingEpisode(false);
+ }
+ };
+
const validRecords = grantedRecords.filter((r) => {
const recId = getRecordId(r);
if (recId === undefined || recId === null) return false;
@@ -265,6 +294,9 @@ export default function DoctorDashboard() {
Patient Vault
+ {selectedPatient && (
+
+ )}
setManualSearchQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSelectPatient(manualSearchQuery)} className="border rounded-lg px-2 py-1 text-[11px] font-mono bg-background w-[140px]" />
{selectedPatient &&
}
@@ -374,6 +406,14 @@ export default function DoctorDashboard() {
setPatientAddressToMint(e.target.value)} className="w-full text-[11px] px-3 py-2.5 border rounded-xl bg-background font-mono" />
)}
setFileToMint(e.target.files?.[0] || null)} className="w-full text-[11px] file:mr-3 file:py-1.5 file:px-3 file:rounded-lg file:border-0 file:bg-muted file:text-foreground cursor-pointer" />
+ {episodes.length > 0 && (
+
+ )}
{isUploading ? : }{isUploading ? 'Minting...' : 'Mint Record'}
@@ -385,6 +425,35 @@ export default function DoctorDashboard() {
+
+ {/* New Episode Modal */}
+
+ {showEpisodeModal && (
+
+
+
+
New Episode of Care
+
+
+
+
+
+ setEpisodeTitle(e.target.value)} disabled={isCreatingEpisode} placeholder="e.g. Post-Op Cardiac Recovery" className="w-full px-3 py-2 rounded-xl border border-border bg-background/50 text-sm focus:outline-none focus:ring-2 focus:ring-secondary/40 disabled:opacity-50" />
+
+
+
+
+ {selectedPatient &&
Patient: {selectedPatient.slice(0, 14)}...
}
+
+
+
+
+ )}
+
);
}
diff --git a/Frontend/src/components/dashboard/InsurerDashboard.jsx b/Frontend/src/components/dashboard/InsurerDashboard.jsx
index 761ecc8..156b9c3 100644
--- a/Frontend/src/components/dashboard/InsurerDashboard.jsx
+++ b/Frontend/src/components/dashboard/InsurerDashboard.jsx
@@ -88,6 +88,51 @@ function VerificationCheckRow({ icon: Icon, label, description, verified, delay
);
}
+function TrustScore({ providerVerified, integrityValid, isLatestVersion }) {
+ const score = (providerVerified ? 40 : 0) + (integrityValid ? 40 : 0) + (isLatestVersion ? 20 : 10);
+ const tier = score >= 90 ? 'HIGH' : score >= 60 ? 'MEDIUM' : 'LOW';
+ const colors = {
+ HIGH: { bg: 'bg-emerald-900/20', border: 'border-emerald-500/40', text: 'text-emerald-400', dot: 'bg-emerald-500' },
+ MEDIUM: { bg: 'bg-yellow-900/20', border: 'border-yellow-500/40', text: 'text-yellow-400', dot: 'bg-yellow-500' },
+ LOW: { bg: 'bg-red-900/20', border: 'border-red-500/40', text: 'text-red-400', dot: 'bg-red-500' },
+ };
+ const c = colors[tier];
+ const reasons = [];
+ if (!providerVerified) reasons.push('Provider signature could not be verified');
+ if (!integrityValid) reasons.push('Cryptographic integrity check failed');
+ if (!isLatestVersion) reasons.push('Record has been superseded by a newer version');
+
+ return (
+
+
+
+ {score}
+ / 100
+
+
+ {reasons.length > 0 && (
+
+ {reasons.map((r, i) => (
+ -
+ {r}
+
+ ))}
+
+ )}
+
+ );
+}
+
+
export default function InsuranceDashboard() {
const account = useActiveAccount();
const { user, logout } = useAuth();
@@ -235,6 +280,12 @@ export default function InsuranceDashboard() {
+
+
{verificationResult.signature && verificationResult.hashMatch ? (
diff --git a/Frontend/src/components/dashboard/PatientDashboard.jsx b/Frontend/src/components/dashboard/PatientDashboard.jsx
index dbc89da..3375298 100644
--- a/Frontend/src/components/dashboard/PatientDashboard.jsx
+++ b/Frontend/src/components/dashboard/PatientDashboard.jsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useActiveAccount, useDisconnect, useActiveWallet } from 'thirdweb/react';
-import { getPatientVault, grantAccess, revokeAccess, checkInToClinic, getActiveGrants, getPatientCheckInStatus, leaveClinic } from '../../services/api';
+import { getPatientVault, grantAccess, revokeAccess, checkInToClinic, getActiveGrants, getPatientCheckInStatus, leaveClinic, getPatientEpisodes } from '../../services/api';
// FIX 1: Alias 'History' to 'HistoryIcon' to prevent "Illegal Constructor" error
import {
@@ -50,7 +50,7 @@ const getCid = (r) => {
const FALLBACK_CHART_DATA = [{ month: 'Jan', v: 0 }, { month: 'Feb', v: 0 }, { month: 'Mar', v: 0 }, { month: 'Apr', v: 0 }, { month: 'May', v: 0 }, { month: 'Jun', v: 0 }, { month: 'Jul', v: 0 }, { month: 'Aug', v: 0 }, { month: 'Sep', v: 0 }, { month: 'Oct', v: 0 }, { month: 'Nov', v: 0 }, { month: 'Dec', v: 0 }];
const NAV = {
- main: [{ id: 'overview', label: 'Overview', icon: LayoutDashboard }, { id: 'records', label: 'My Records', icon: FileText }, { id: 'access', label: 'Access Control', icon: ShieldCheck }, { id: 'appointments', label: 'Appointments', icon: Calendar }],
+ main: [{ id: 'overview', label: 'Overview', icon: LayoutDashboard }, { id: 'records', label: 'My Records', icon: FileText }, { id: 'episodes', label: 'Episodes', icon: ClipboardList }, { id: 'access', label: 'Access Control', icon: ShieldCheck }, { id: 'appointments', label: 'Appointments', icon: Calendar }],
features: [{ id: 'prescriptions', label: 'Prescriptions', icon: ClipboardList }, { id: 'scans', label: 'Scans & Imaging', icon: Bot }, { id: 'notifications', label: 'Notifications', icon: Bell }],
general: [{ id: 'settings', label: 'Settings', icon: Settings }, { id: 'logout', label: 'Log out', icon: LogOut }],
};
@@ -172,6 +172,8 @@ export default function PatientDashboard() {
const [isCheckingIn, setIsCheckingIn] = useState(false);
const [activeCheckIn, setActiveCheckIn] = useState(() => localStorage.getItem('medichain_checkin') || null);
const [activeGrants, setActiveGrants] = useState([]);
+ const [episodes, setEpisodes] = useState(null);
+ const [loadingEpisodes, setLoadingEpisodes] = useState(false);
// --- FIX 3: Robust Viewing Handler ---
const handleViewDocument = (e, rawCid) => {
@@ -196,6 +198,13 @@ export default function PatientDashboard() {
catch (err) { setActiveGrants([]); }
}, []);
+ const fetchEpisodes = useCallback(async () => {
+ setLoadingEpisodes(true);
+ try { const res = await getPatientEpisodes(); setEpisodes(res.data || { episodes: [], ungroupedRecords: [] }); }
+ catch (err) { setEpisodes({ episodes: [], ungroupedRecords: [] }); }
+ finally { setLoadingEpisodes(false); }
+ }, []);
+
const syncCheckInStatus = useCallback(async () => {
try {
const res = await getPatientCheckInStatus();
@@ -217,6 +226,10 @@ export default function PatientDashboard() {
return () => clearInterval(interval);
}, [fetchRecords, fetchGrants, syncCheckInStatus]);
+ useEffect(() => {
+ if (activeNav === 'episodes' && !episodes) fetchEpisodes();
+ }, [activeNav, episodes, fetchEpisodes]);
+
const handleLogout = () => {
if (wallet) disconnect(wallet);
logout();
@@ -394,6 +407,79 @@ export default function PatientDashboard() {
+
+ {/* Episodes of Care View */}
+ {activeNav === 'episodes' && (
+
+
+
+
Episodes of Care
+
+
+
+ {loadingEpisodes ? (
+
+ ) : !episodes ? null : (
+ <>
+ {episodes.episodes && episodes.episodes.length > 0 ? episodes.episodes.map(ep => (
+
+
+
+
+
{ep.title}
+ {ep.description &&
{ep.description}
}
+
+
+ {(ep.records || []).length} record{ep.records?.length !== 1 ? 's' : ''}
+
+
+ Created by: {(ep.createdBy || '').slice(0, 14)}...
+ {ep.records && ep.records.length > 0 ? (
+
+ {ep.records.map(r => (
+
handleViewDocument(e, getCid(r))}>
+
+
{r.recordType || 'Medical Record'}
+
#{r.recordId}
+
+
+ ))}
+
+ ) : (
+ No records linked to this episode yet.
+ )}
+
+
+ )) : (
+
No episodes of care found.
+ )}
+
+ {/* Ungrouped Records */}
+ {episodes.ungroupedRecords && episodes.ungroupedRecords.length > 0 && (
+
+
+
+
+ Ungrouped Records
+ {episodes.ungroupedRecords.length}
+
+
+ {episodes.ungroupedRecords.map(r => (
+
handleViewDocument(e, getCid(r))}>
+
+
{r.recordType || 'Medical Record'}
+
#{r.recordId}
+
+
+ ))}
+
+
+
+ )}
+ >
+ )}
+
+ )}
{
diff --git a/Frontend/src/services/api.js b/Frontend/src/services/api.js
index 0a2fdd5..38befdf 100644
--- a/Frontend/src/services/api.js
+++ b/Frontend/src/services/api.js
@@ -80,6 +80,7 @@ export async function getPatientVault() {
return { ...res, data: (res.data || []).map(normalizeRecord) };
}
export function checkInToClinic(doctorAddress) { return request('POST', '/api/v1/dashboard/patient/check-in', { doctorAddress }); }
+export function getPatientEpisodes() { return request('GET', '/api/v1/dashboard/patient/episodes'); }
export async function getActiveGrants() {
const res = await request('GET', '/api/v1/blockchain/active-grants');
@@ -102,6 +103,9 @@ export function leaveClinic() { return request('POST', '/api/v1/dashboard/patien
// --- Doctor Methods ---
export function getWaitingRoom() { return request('GET', '/api/v1/dashboard/doctor/waiting-room'); }
export function completeAppointment(checkInId) { return request('POST', '/api/v1/dashboard/doctor/complete-appointment', { checkInId }); }
+export function createEpisode(patientAddress, title, description = '') {
+ return request('POST', '/api/v1/dashboard/doctor/create-episode', { patientAddress, title, description });
+}
export async function getAccessibleRecords(patientAddress) {
const res = await request('GET', `/api/v1/dashboard/doctor/accessible-records/${normalizeWallet(patientAddress)}`);
@@ -118,8 +122,8 @@ export async function getAccessibleRecords(patientAddress) {
}
// --- Blockchain Methods ---
-export function mintRecord(patientAddress, cid, recordType = 'Medical Record', previousRecordId = null) {
- return request('POST', '/api/v1/blockchain/mint', { patientAddress, cid, recordType, previousRecordId });
+export function mintRecord(patientAddress, cid, recordType = 'Medical Record', previousRecordId = null, episodeId = null) {
+ return request('POST', '/api/v1/blockchain/mint', { patientAddress, cid, recordType, previousRecordId, episodeId });
}
export function amendRecord(patientAddress, cid, previousRecordId, recordType = 'Medical Record') {
diff --git a/backend/mvnw b/backend/mvnw
old mode 100644
new mode 100755
diff --git a/backend/src/main/java/org/medichain/backend/controller/BlockchainController.java b/backend/src/main/java/org/medichain/backend/controller/BlockchainController.java
index e50d4f7..3a6e394 100644
--- a/backend/src/main/java/org/medichain/backend/controller/BlockchainController.java
+++ b/backend/src/main/java/org/medichain/backend/controller/BlockchainController.java
@@ -27,12 +27,13 @@ public BlockchainController(BlockchainService blockchainService) {
@PostMapping("/mint")
public ResponseEntity> mintRecord(@RequestBody MintRecordRequest request) {
try {
- // Pass the recordType to the service
+ // Pass the recordType and optional episodeId to the service
String txHash = blockchainService.mintMedicalRecord(
request.getPatientAddress(),
request.getCid(),
request.getPreviousRecordId(),
- request.getRecordType()
+ request.getRecordType(),
+ request.getEpisodeId()
);
return ResponseEntity.ok(Map.of(
"status", "success",
diff --git a/backend/src/main/java/org/medichain/backend/controller/DashboardController.java b/backend/src/main/java/org/medichain/backend/controller/DashboardController.java
index aec1547..08e9d0f 100644
--- a/backend/src/main/java/org/medichain/backend/controller/DashboardController.java
+++ b/backend/src/main/java/org/medichain/backend/controller/DashboardController.java
@@ -3,7 +3,10 @@
import lombok.extern.slf4j.Slf4j;
import org.medichain.backend.dto.CheckInRequest;
import org.medichain.backend.dto.CompleteAppointmentRequest;
+import org.medichain.backend.dto.CreateEpisodeRequest;
+import org.medichain.backend.entity.Episode;
import org.medichain.backend.service.DashboardService;
+import org.medichain.backend.service.EpisodeService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@@ -15,19 +18,21 @@
@CrossOrigin(origins = "*")
@Slf4j
public class DashboardController {
-
+
private final DashboardService dashboardService;
-
- public DashboardController(DashboardService dashboardService) {
+ private final EpisodeService episodeService;
+
+ public DashboardController(DashboardService dashboardService, EpisodeService episodeService) {
this.dashboardService = dashboardService;
+ this.episodeService = episodeService;
}
-
+
private String getAuthenticatedWallet() {
return (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
-
+
// --- PATIENT ENDPOINTS ---
-
+
@GetMapping("/patient/vault")
public ResponseEntity> getPatientVault() {
try {
@@ -37,7 +42,7 @@ public ResponseEntity> getPatientVault() {
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}
}
-
+
@PostMapping("/patient/check-in")
public ResponseEntity> checkInToClinic(@RequestBody CheckInRequest request) {
try {
@@ -47,9 +52,19 @@ public ResponseEntity> checkInToClinic(@RequestBody CheckInRequest request) {
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}
}
-
+
+ @GetMapping("/patient/episodes")
+ public ResponseEntity> getPatientEpisodes() {
+ try {
+ var result = episodeService.getPatientEpisodes(getAuthenticatedWallet());
+ return ResponseEntity.ok(Map.of("status", "success", "data", result));
+ } catch (Exception e) {
+ return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
+ }
+ }
+
// --- DOCTOR ENDPOINTS ---
-
+
@GetMapping("/doctor/waiting-room")
public ResponseEntity> getWaitingRoom() {
try {
@@ -59,7 +74,7 @@ public ResponseEntity> getWaitingRoom() {
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}
}
-
+
@GetMapping("/doctor/accessible-records/{patientAddress}")
public ResponseEntity> getAccessibleRecords(@PathVariable String patientAddress) {
try {
@@ -69,7 +84,7 @@ public ResponseEntity> getAccessibleRecords(@PathVariable String patientAddres
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}
}
-
+
@PostMapping("/doctor/complete-appointment")
public ResponseEntity> completeAppointment(@RequestBody CompleteAppointmentRequest request) {
try {
@@ -79,7 +94,30 @@ public ResponseEntity> completeAppointment(@RequestBody CompleteAppointmentReq
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}
}
-
+
+ @PostMapping("/doctor/create-episode")
+ public ResponseEntity> createEpisode(@RequestBody CreateEpisodeRequest request) {
+ try {
+ String doctorWallet = getAuthenticatedWallet();
+ Episode episode = episodeService.createEpisode(
+ request.getPatientAddress(),
+ request.getTitle(),
+ request.getDescription(),
+ doctorWallet
+ );
+ return ResponseEntity.ok(Map.of("status", "success", "data", Map.of(
+ "episodeId", episode.getEpisodeId(),
+ "patientAddress", episode.getPatientAddress(),
+ "title", episode.getTitle(),
+ "description", episode.getDescription() != null ? episode.getDescription() : "",
+ "createdBy", episode.getCreatedBy(),
+ "createdAt", episode.getCreatedAt().toString()
+ )));
+ } catch (Exception e) {
+ return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
+ }
+ }
+
@GetMapping("/patient/check-in-status")
public ResponseEntity> getCheckInStatus() {
try {
@@ -89,7 +127,7 @@ public ResponseEntity> getCheckInStatus() {
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}
}
-
+
@PostMapping("/patient/leave-room")
public ResponseEntity> leaveWaitingRoom() {
try {
@@ -100,3 +138,4 @@ public ResponseEntity> leaveWaitingRoom() {
}
}
}
+
diff --git a/backend/src/main/java/org/medichain/backend/dto/CreateEpisodeRequest.java b/backend/src/main/java/org/medichain/backend/dto/CreateEpisodeRequest.java
new file mode 100644
index 0000000..baed932
--- /dev/null
+++ b/backend/src/main/java/org/medichain/backend/dto/CreateEpisodeRequest.java
@@ -0,0 +1,10 @@
+package org.medichain.backend.dto;
+
+import lombok.Data;
+
+@Data
+public class CreateEpisodeRequest {
+ private String patientAddress;
+ private String title;
+ private String description; // optional
+}
diff --git a/backend/src/main/java/org/medichain/backend/dto/MintRecordRequest.java b/backend/src/main/java/org/medichain/backend/dto/MintRecordRequest.java
index 97c1da8..3a444dd 100644
--- a/backend/src/main/java/org/medichain/backend/dto/MintRecordRequest.java
+++ b/backend/src/main/java/org/medichain/backend/dto/MintRecordRequest.java
@@ -8,4 +8,5 @@ public class MintRecordRequest {
private String cid; // The IPFS hash of the medical file
private Long previousRecordId; // If amending, the record ID being superseded (null for new records)
private String recordType;
+ private Long episodeId; // Optional: link to an episode
}
diff --git a/backend/src/main/java/org/medichain/backend/entity/Episode.java b/backend/src/main/java/org/medichain/backend/entity/Episode.java
new file mode 100644
index 0000000..750a8ed
--- /dev/null
+++ b/backend/src/main/java/org/medichain/backend/entity/Episode.java
@@ -0,0 +1,32 @@
+package org.medichain.backend.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "episodes")
+@Data
+public class Episode {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "episode_id")
+ private Long episodeId;
+
+ @Column(name = "patient_address", nullable = false, length = 42)
+ private String patientAddress;
+
+ @Column(name = "title", nullable = false)
+ private String title;
+
+ @Column(name = "description")
+ private String description;
+
+ @Column(name = "created_by", nullable = false, length = 42)
+ private String createdBy;
+
+ @Column(name = "created_at", nullable = false)
+ private LocalDateTime createdAt = LocalDateTime.now();
+}
diff --git a/backend/src/main/java/org/medichain/backend/entity/MedicalRecord.java b/backend/src/main/java/org/medichain/backend/entity/MedicalRecord.java
index b482ca5..1d065c0 100644
--- a/backend/src/main/java/org/medichain/backend/entity/MedicalRecord.java
+++ b/backend/src/main/java/org/medichain/backend/entity/MedicalRecord.java
@@ -32,4 +32,7 @@ public class MedicalRecord {
@Column(name = "tx_hash", nullable = false)
private String txHash; // To prove to the user that it's actually on the blockchain
+
+ @Column(name = "episode_id")
+ private Long episodeId; // Nullable FK to episodes(episode_id)
}
diff --git a/backend/src/main/java/org/medichain/backend/repository/EpisodeRepository.java b/backend/src/main/java/org/medichain/backend/repository/EpisodeRepository.java
new file mode 100644
index 0000000..2d1cfd8
--- /dev/null
+++ b/backend/src/main/java/org/medichain/backend/repository/EpisodeRepository.java
@@ -0,0 +1,12 @@
+package org.medichain.backend.repository;
+
+import org.medichain.backend.entity.Episode;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface EpisodeRepository extends JpaRepository {
+ List findByPatientAddressIgnoreCaseOrderByCreatedAtDesc(String patientAddress);
+}
diff --git a/backend/src/main/java/org/medichain/backend/service/BlockchainService.java b/backend/src/main/java/org/medichain/backend/service/BlockchainService.java
index e02941f..a67b60d 100644
--- a/backend/src/main/java/org/medichain/backend/service/BlockchainService.java
+++ b/backend/src/main/java/org/medichain/backend/service/BlockchainService.java
@@ -75,7 +75,7 @@ private String getAuthenticatedWalletAddress() {
}
@Transactional
- public String mintMedicalRecord(String patientWalletAddress, String ipfsCid, Long previousRecordId, String recordType) {
+ public String mintMedicalRecord(String patientWalletAddress, String ipfsCid, Long previousRecordId, String recordType, Long episodeId) {
try {
String doctorAddress = getAuthenticatedWalletAddress();
@@ -102,6 +102,7 @@ public String mintMedicalRecord(String patientWalletAddress, String ipfsCid, Lon
record.setSuperseded(false);
record.setPreviousRecordId(previousRecordId);
record.setTxHash(transactionHash);
+ record.setEpisodeId(episodeId); // nullable — null when not provided
medicalRecordRepository.save(record);
return transactionHash;
diff --git a/backend/src/main/java/org/medichain/backend/service/EpisodeService.java b/backend/src/main/java/org/medichain/backend/service/EpisodeService.java
new file mode 100644
index 0000000..e8e5cc2
--- /dev/null
+++ b/backend/src/main/java/org/medichain/backend/service/EpisodeService.java
@@ -0,0 +1,75 @@
+package org.medichain.backend.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.medichain.backend.entity.Episode;
+import org.medichain.backend.entity.MedicalRecord;
+import org.medichain.backend.repository.EpisodeRepository;
+import org.medichain.backend.repository.MedicalRecordRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class EpisodeService {
+
+ private final EpisodeRepository episodeRepository;
+ private final MedicalRecordRepository medicalRecordRepository;
+
+ public EpisodeService(EpisodeRepository episodeRepository,
+ MedicalRecordRepository medicalRecordRepository) {
+ this.episodeRepository = episodeRepository;
+ this.medicalRecordRepository = medicalRecordRepository;
+ }
+
+ @Transactional
+ public Episode createEpisode(String patientAddress, String title, String description, String createdBy) {
+ Episode episode = new Episode();
+ episode.setPatientAddress(patientAddress);
+ episode.setTitle(title);
+ episode.setDescription(description);
+ episode.setCreatedBy(createdBy);
+ return episodeRepository.save(episode);
+ }
+
+ /**
+ * Returns all episodes for a patient with nested records grouped under each episode.
+ * Also includes an "ungrouped" section for records where episode_id is null.
+ */
+ public Map getPatientEpisodes(String patientAddress) {
+ List episodes = episodeRepository.findByPatientAddressIgnoreCaseOrderByCreatedAtDesc(patientAddress);
+ List allRecords = medicalRecordRepository.findByPatientAddressIgnoreCase(patientAddress);
+
+ // Build grouped episodes list
+ List