Compare commits
77 Commits
Mariya_Var
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
048bfcd0cb | ||
|
|
565cb2259a | ||
|
|
644e06f78f | ||
|
|
41aebcec7b | ||
|
|
8e659d8fed | ||
|
|
0ed5e0725b | ||
|
|
aa2c04f853 | ||
|
|
47579b0796 | ||
|
|
ff8c62ef46 | ||
|
|
f40a78b843 | ||
|
|
79980e34c8 | ||
|
|
d9fe2fdf25 | ||
|
|
0c5b13fc67 | ||
|
|
83dc9f068f | ||
|
|
7e3226c6d2 | ||
|
|
870c3a4dcb | ||
|
|
f7071a2ff5 | ||
|
|
ed8dd0d4d6 | ||
|
|
dd92c78104 | ||
|
|
6872d260b6 | ||
|
|
3e4f08b90e | ||
|
|
b69791c246 | ||
|
|
d327fecf8c | ||
|
|
43375556bf | ||
|
|
92feb4081c | ||
|
|
bb968b4781 | ||
|
|
a751b55647 | ||
|
|
099b3f08fa | ||
|
|
1f55978521 | ||
|
|
84740e846f | ||
|
|
09d18106b8 | ||
|
|
5dbff78d79 | ||
|
|
75ad2aba82 | ||
|
|
3a355ffe57 | ||
|
|
4da937673c | ||
|
|
48c9bacb4f | ||
|
|
4dd9a31a75 | ||
|
|
5d5325d51d | ||
|
|
f857e57173 | ||
|
|
c20197c40a | ||
|
|
ca5106d613 | ||
|
|
9c782b7cd1 | ||
|
|
707e338d3a | ||
|
|
367c36e3f8 | ||
|
|
fc31aea95b | ||
|
|
eaf87791bd | ||
|
|
89090ff86b | ||
|
|
515ad41329 | ||
|
|
920858a523 | ||
|
|
b43aba97f2 | ||
|
|
16aa545862 | ||
|
|
a836335ed3 | ||
|
|
65723718c6 | ||
|
|
fb3d715831 | ||
|
|
683c6049af | ||
|
|
34a770ceed | ||
|
|
67a7414d5c | ||
|
|
260e6ec734 | ||
|
|
eb975ce11a | ||
|
|
a3d2bea0cc | ||
|
|
420171d24a | ||
|
|
dd4ee6d964 | ||
|
|
62810fe81b | ||
|
|
aa08336f5f | ||
|
|
66838b6360 | ||
|
|
3774972839 | ||
|
|
924e48c1f0 | ||
|
|
36879b6326 | ||
|
|
327aa07735 | ||
|
|
f269b12da1 | ||
|
|
cb0063e286 | ||
|
|
3e01c94e46 | ||
|
|
cde2485a60 | ||
|
|
9893161684 | ||
|
|
eecc6501df | ||
|
|
47d348d791 | ||
|
|
16b3f521d4 |
27
.firebaserc
@@ -1,5 +1,30 @@
|
|||||||
{
|
{
|
||||||
"projects": {
|
"projects": {
|
||||||
"default": "hpos-af3cc"
|
"default": "hpos-af3cc"
|
||||||
}
|
},
|
||||||
|
"targets": {
|
||||||
|
"hpos-af3cc": {
|
||||||
|
"hosting": {
|
||||||
|
"dev1": [
|
||||||
|
"hpos-dev1"
|
||||||
|
],
|
||||||
|
"qa": [
|
||||||
|
"hpos-qa"
|
||||||
|
],
|
||||||
|
"dev": [
|
||||||
|
"hpos-dev"
|
||||||
|
],
|
||||||
|
"preprod": [
|
||||||
|
"hpos-preprod"
|
||||||
|
],
|
||||||
|
"uat": [
|
||||||
|
"hpos-uat"
|
||||||
|
],
|
||||||
|
"iocl": [
|
||||||
|
"iocl-iisc"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"etags": {}
|
||||||
}
|
}
|
||||||
14
.gitignore
vendored
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
@@ -27,3 +28,16 @@ yarn-error.log*
|
|||||||
src/components/testData.json
|
src/components/testData.json
|
||||||
public/world-110m.json
|
public/world-110m.json
|
||||||
public/india-states.json
|
public/india-states.json
|
||||||
|
*.apk
|
||||||
|
|
||||||
|
scripts/reports
|
||||||
|
scripts/data
|
||||||
|
firebase-debug.log
|
||||||
|
.env.preprod
|
||||||
|
.env.prod
|
||||||
|
.env.qa
|
||||||
|
.env.dev
|
||||||
|
.env.uat
|
||||||
|
.env.iocl
|
||||||
|
|
||||||
|
certificate_path/
|
||||||
|
|||||||
18
.gitlab-ci.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
stages:
|
||||||
|
- test
|
||||||
|
|
||||||
|
unit-test:
|
||||||
|
image: node:latest
|
||||||
|
stage: test
|
||||||
|
before_script:
|
||||||
|
- npm install --force
|
||||||
|
script:
|
||||||
|
- npm run test-ci
|
||||||
|
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage/
|
||||||
|
when: always
|
||||||
|
reports:
|
||||||
|
junit:
|
||||||
|
- junit.xml
|
||||||
22
README.md
@@ -14,4 +14,26 @@
|
|||||||
http://localhost:3000
|
http://localhost:3000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### add target (multi site), if not already defined for that site
|
||||||
|
|
||||||
|
firebase target:apply hosting dev1 hpos-dev1
|
||||||
|
|
||||||
|
### add or change to firebase.json
|
||||||
|
|
||||||
|
"target": "dev1",
|
||||||
|
|
||||||
|
### run build
|
||||||
|
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
|
||||||
|
### deploy
|
||||||
|
|
||||||
|
firebase deploy --only hosting:dev
|
||||||
|
|
||||||
pyinstaller --noconfirm --noconsole -i "smi.ico" --windowed --clean hemocube.py
|
pyinstaller --noconfirm --noconsole -i "smi.ico" --windowed --clean hemocube.py
|
||||||
|
|
||||||
|
### known issues
|
||||||
|
|
||||||
|
gemini starts giving error after 70 odd messages in a chat discussion
|
||||||
|
|||||||
1
__mocks__/styleMock.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = {};
|
||||||
1
__mocks__/svgMock.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = 'test-file-stub';
|
||||||
4
babel.config.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"hosting": {
|
"hosting": {
|
||||||
|
"target": "iocl",
|
||||||
"public": "build",
|
"public": "build",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"firebase.json",
|
"firebase.json",
|
||||||
|
|||||||
19
jest.config.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
module.exports = {
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
|
||||||
|
transform: {
|
||||||
|
"^.+\\.jsx?$": "babel-jest",
|
||||||
|
'^.+\\.mjs': 'babel-jest',
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
'/node_modules/',
|
||||||
|
'node_modules/(?!(d3|other-module-to-transform)/)',
|
||||||
|
'\\.css$',
|
||||||
|
],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'\\.(svg)$': '<rootDir>/__mocks__/svgMock.js',
|
||||||
|
'\\.(css|less|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
|
||||||
|
'^d3$': '<rootDir>/node_modules/d3/src/index.js',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
BIN
logo_nobg.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
74
nats.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// const nats = require('nats');
|
||||||
|
// const fs = require('fs');
|
||||||
|
|
||||||
|
// const privateKey = fs.readFileSync('clientCertificate/client-key.pem');
|
||||||
|
// const certificate = fs.readFileSync('clientCertificate/client-cert.pem');
|
||||||
|
|
||||||
|
// const nc = nats.connect({
|
||||||
|
// servers: ['nats://nanodgx.in:4222'],
|
||||||
|
// token: 'SUADA3R4HRQG5ZEGR7E65P5P2JUQ2LMVLEOGJWEIKLEYOZEPQES2WHW2KU',
|
||||||
|
// tls: {
|
||||||
|
// key: privateKey,
|
||||||
|
// cert: certificate,
|
||||||
|
// },
|
||||||
|
// connect: function() {
|
||||||
|
// console.log('Connected to NATS server');
|
||||||
|
|
||||||
|
// // Subscribe to a subject
|
||||||
|
// const subscription = nc.subscribe('your-subject');
|
||||||
|
|
||||||
|
// // Listen for incoming messages
|
||||||
|
// subscription.on('message', function(msg) {
|
||||||
|
// console.log(`Received message: ${msg.getData()}`);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Publish a message
|
||||||
|
// nc.publish('your-subject', 'Hello NATS!');
|
||||||
|
// },
|
||||||
|
// error: function(err) {
|
||||||
|
// console.error(`Error connecting to NATS server: ${err}`);
|
||||||
|
// },
|
||||||
|
// close: function() {
|
||||||
|
// console.log('Connection to NATS server closed');
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
const NATS = require('nats');
|
||||||
|
const sc = NATS.StringCodec();
|
||||||
|
const seed = new TextEncoder().encode(
|
||||||
|
"SUACFBL4SJNFOYVB6F4AHK7A7VCHDENCKVLKJTTKAH3I47BOTSOE3Z2K7M",
|
||||||
|
);
|
||||||
|
const servers = [
|
||||||
|
{
|
||||||
|
servers: "nats://nanodgx.in:4222",
|
||||||
|
|
||||||
|
authenticator: NATS.nkeyAuthenticator(seed),
|
||||||
|
tls: {
|
||||||
|
keyFile: "certificate_path/client-key.pem",
|
||||||
|
certFile: "certificate_path/client-cert.pem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
servers.forEach(async (v) => {
|
||||||
|
try {
|
||||||
|
// console.log(v);
|
||||||
|
const nc = await NATS.connect(v);
|
||||||
|
|
||||||
|
// Subscribe to a subject
|
||||||
|
const subscription = await nc.subscribe("server.hpos.HCV-000-3998.ping");
|
||||||
|
/*
|
||||||
|
(async () => {
|
||||||
|
for await (const msg of subscription) {
|
||||||
|
console.log(`Received message: ${sc.decode(msg.data)}`);
|
||||||
|
// Your custom logic for handling the incoming message goes here
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
*/
|
||||||
|
nc.publish("server.hpos.HCV-000-3998.ping", "ALIVE");
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error connecting to ${JSON.stringify(v)} ${err}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
16278
package-lock.json
generated
27
package.json
@@ -3,6 +3,11 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.11.1",
|
||||||
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"@mui/icons-material": "^5.14.1",
|
||||||
|
"@mui/material": "^5.14.2",
|
||||||
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
@@ -10,17 +15,26 @@
|
|||||||
"d3": "^7.8.5",
|
"d3": "^7.8.5",
|
||||||
"d3-selection": "^3.0.0",
|
"d3-selection": "^3.0.0",
|
||||||
"firebase": "^9.23.0",
|
"firebase": "^9.23.0",
|
||||||
|
"katex": "^0.16.9",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"ml-savitzky-golay-generalized": "^4.0.1",
|
"ml-savitzky-golay-generalized": "^4.0.1",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
|
"nats": "^2.19.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-datepicker": "^4.16.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropdown": "^1.11.0",
|
"react-dropdown": "^1.11.0",
|
||||||
"react-flip-numbers": "^3.0.8",
|
"react-flip-numbers": "^3.0.8",
|
||||||
"react-icons": "^4.10.1",
|
"react-icons": "^4.10.1",
|
||||||
|
"react-katex": "^3.0.1",
|
||||||
|
"react-markdown": "^9.0.1",
|
||||||
|
"react-mathjax": "^1.0.1",
|
||||||
|
"react-redux": "^8.1.2",
|
||||||
"react-router-dom": "^6.14.2",
|
"react-router-dom": "^6.14.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-select": "^5.7.4",
|
"react-select": "^5.7.4",
|
||||||
|
"remark-gfm": "^4.0.0",
|
||||||
|
"remark-math": "^6.0.0",
|
||||||
"topojson-client": "^3.1.0",
|
"topojson-client": "^3.1.0",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
@@ -30,7 +44,8 @@
|
|||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"deploy": "npm run build && firebase deploy",
|
"deploy": "npm run build && firebase deploy",
|
||||||
"coverage": "react-scripts test --coverage"
|
"coverage": "react-scripts test --coverage",
|
||||||
|
"test-ci": "jest --testPathIgnorePatterns=src/tests/ --passWithNoTests --ci --bail"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -49,5 +64,15 @@
|
|||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.23.7",
|
||||||
|
"@babel/preset-env": "^7.23.8",
|
||||||
|
"@babel/preset-react": "^7.23.3",
|
||||||
|
"@babel/preset-typescript": "^7.23.3",
|
||||||
|
"csv-parser": "^3.0.0",
|
||||||
|
"html-pdf-node": "^1.0.8",
|
||||||
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"ts-jest": "^29.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,11 @@
|
|||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>HPOS Sickle Cell Test Dashboard</title>
|
<title>HPOS Sickle Cell Test Dashboard</title>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
async
|
||||||
|
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML-full"
|
||||||
|
></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
281
scripts/pdf.js
Normal file
16
src/App.js
@@ -2,11 +2,27 @@ import './App.css';
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
|
import MainLayout from "./components/MainLayout";
|
||||||
|
import { routes } from "./routes";
|
||||||
|
|
||||||
import { collection, onSnapshot, query } from "firebase/firestore";
|
import { collection, onSnapshot, query } from "firebase/firestore";
|
||||||
import Card from './components/Card';
|
import Card from './components/Card';
|
||||||
import db from './firebase';
|
import db from './firebase';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<MainLayout />}>
|
||||||
|
{routes}
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function App1() {
|
||||||
const [totalScreened, setTotalScreened] = useState(0);
|
const [totalScreened, setTotalScreened] = useState(0);
|
||||||
const [males, setMales] = useState(0);
|
const [males, setMales] = useState(0);
|
||||||
const [females, setFemales] = useState(0);
|
const [females, setFemales] = useState(0);
|
||||||
|
|||||||
BIN
src/assets/auto_analysis_help-1.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
src/assets/auto_analysis_help-2.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
src/assets/auto_analysis_help-3.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
src/assets/hpos-testing_preprod.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/hpos_testing_preprod_2.1.10d.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/raw_data_help_1.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
src/assets/raw_data_help_2.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
src/assets/raw_data_help_3.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src/assets/registration_help_1.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
src/assets/registration_help_2.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
src/assets/registration_help_3.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
src/assets/testing_2_1_10.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
63
src/components/Accuracy.css
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
.button {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
/* Green */
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
/* padding: 15px 32px; */
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-row {
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-label {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dates {
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-date {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-date {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-row {
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
animation: rotate 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
107
src/components/Accuracy.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FaInfoCircle } from "react-icons/fa";
|
||||||
|
import { AiOutlineLoading } from "react-icons/ai";
|
||||||
|
import DatePicker from "react-datepicker";
|
||||||
|
import moment from 'moment';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
|
import './Accuracy.css';
|
||||||
|
|
||||||
|
function Accuracy({ show }) {
|
||||||
|
const [showCardInfo, setShowCardInfo] = useState(false);
|
||||||
|
const [startDate, setStartDate] = useState("");
|
||||||
|
const [endDate, setEndDate] = useState("");
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null); // New state for handling errors
|
||||||
|
|
||||||
|
const handleFileChange = (event) => {
|
||||||
|
setSelectedFile(event.target.files[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const fetchFile = async (start_date, end_date) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null); // Reset error state before making a new request
|
||||||
|
|
||||||
|
if (!selectedFile) {
|
||||||
|
const formData = new FormData();
|
||||||
|
// formData.append('file', selectedFile);
|
||||||
|
|
||||||
|
const queryString = `start_date=${moment(startDate).format(
|
||||||
|
'YYYY-MM-DD'
|
||||||
|
)}&end_date=${moment(endDate).format('YYYY-MM-DD')}`;
|
||||||
|
|
||||||
|
const url = `https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/performance?${queryString}`;
|
||||||
|
|
||||||
|
// Make a POST request using the Fetch API
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
// Add any necessary headers
|
||||||
|
// 'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the request was successful (status code 2xx)
|
||||||
|
if (response.ok) {
|
||||||
|
// Create a blob from the response data
|
||||||
|
const blob = await response.blob();
|
||||||
|
|
||||||
|
// Create a download link and trigger a click to download the file
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = 'autoanlysis.xlsx'; // Set the desired file name
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
document.body.removeChild(link);
|
||||||
|
} else {
|
||||||
|
console.error('Error downloading file:', response.statusText);
|
||||||
|
setError('Error downloading file. Please try again.'); // Set error message
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError("Select file from reference device");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading file:', error);
|
||||||
|
setError('Error downloading file. Please try again.'); // Set error message
|
||||||
|
} finally {
|
||||||
|
setLoading(false); // Set loading back to false when the request completes
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Auto Analysis</h1>
|
||||||
|
{/* <div className='file-row'>
|
||||||
|
<div className='file-label'>Reference file</div>
|
||||||
|
<input type="file" onChange={handleFileChange} />
|
||||||
|
</div> */}
|
||||||
|
<div className='dates'>
|
||||||
|
<div className='start-date'>
|
||||||
|
<DatePicker selected={startDate} onChange={(date) => setStartDate(date)} placeholderText='start date' />
|
||||||
|
</div>
|
||||||
|
<div className='end-date'>
|
||||||
|
<DatePicker selected={endDate} onChange={(date) => setEndDate(date)} placeholderText='end date' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='download-row'>
|
||||||
|
<div className='button' onClick={() => fetchFile(startDate, endDate)}>
|
||||||
|
{loading ? (
|
||||||
|
<AiOutlineLoading className="loading-icon" />
|
||||||
|
) : (
|
||||||
|
'Download Excel'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{error && <div className="error-message">{error}</div>} {/* Display error message if there's an error */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Accuracy;
|
||||||
0
src/components/Alerts.css
Normal file
39
src/components/Alerts.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import { query, collection, onSnapshot } from 'firebase/firestore';
|
||||||
|
|
||||||
|
import './Alerts.css';
|
||||||
|
import db from '../firebase';
|
||||||
|
|
||||||
|
const Alerts = () => {
|
||||||
|
const [alerts, setAlerts] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!allTests) {
|
||||||
|
const q = query(collection(db, "alerts"));
|
||||||
|
|
||||||
|
const unsub = onSnapshot(q, (querySnapshot) => {
|
||||||
|
const testDevices = new Set();
|
||||||
|
const hposTests = [];
|
||||||
|
querySnapshot.forEach((document) => {
|
||||||
|
const hposTestData = document.data();
|
||||||
|
hposTests.push(hposTestData);
|
||||||
|
const { deviceSerialNumber } = hposTestData;
|
||||||
|
testDevices.add(deviceSerialNumber);
|
||||||
|
});
|
||||||
|
const groups = [...testDevices];
|
||||||
|
// setDevices(groups);
|
||||||
|
setAlerts(hposTests);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => unsub;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{alerts.length}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Alerts;
|
||||||
4
src/components/AppInstallation.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.app-qrcode {
|
||||||
|
height: 250px;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
38
src/components/AppInstallation.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import { query, collection, onSnapshot } from 'firebase/firestore';
|
||||||
|
|
||||||
|
import './AppInstallation.css';
|
||||||
|
import db from '../firebase';
|
||||||
|
|
||||||
|
const AppInstallation = () => {
|
||||||
|
const [alerts, setAlerts] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>App Installation</h1>
|
||||||
|
|
||||||
|
<h3>First try the firebase app distribution which is email linked. After adding the email from backend. Check the email to get started.</h3>
|
||||||
|
{/* <div>
|
||||||
|
<div>Testing 2.1.27 (Preprod - 07-11-2023)</div>
|
||||||
|
<a href="https://www.dropbox.com/scl/fi/v0hux8qrby54rjns8mhum/hpos-testing_preprod_2.1.27.apk?rlkey=ias0fe0zq72avy01rk4cvzmvp&dl=1">
|
||||||
|
<img className="app-qrcode" src={require('../assets/hpos-testing_preprod.png')} alt='icon'></img>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>Steps to install:</p>
|
||||||
|
<p>
|
||||||
|
<ul>
|
||||||
|
<li>use an QR code scanner app to download the app</li>
|
||||||
|
<li>if the phone browser prompts an warning, select download anyway</li>
|
||||||
|
<li>install the downloaded apk and give permissions</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppInstallation;
|
||||||
0
src/components/CurveFit.css
Normal file
65
src/components/CurveFit.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { FaInfoCircle } from "react-icons/fa";
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import './CurveFit.css';
|
||||||
|
import ImageDownloader from './ImageDownloader';
|
||||||
|
|
||||||
|
function CurveFit({ show }) {
|
||||||
|
const [showCardInfo, setShowCardInfo] = useState(false);
|
||||||
|
const [svgData, setSvgData] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const fetchSvg = () => {
|
||||||
|
axios({
|
||||||
|
method: 'get',
|
||||||
|
url: "https://asia-south1-hpos-af3cc.cloudfunctions.net/crdboxplot",
|
||||||
|
data: {
|
||||||
|
"_id": "id",
|
||||||
|
"underMedication": false,
|
||||||
|
"underTransfusion": false,
|
||||||
|
"userImageURL": "https://hpos-prod.web.app/logo.png"
|
||||||
|
},
|
||||||
|
withCredentials: false,
|
||||||
|
secure: false,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
// "x-client": "e16a1bd15af2ee640f5a7a18c8d8333f5f01f7c66e003bbc21ee52870f1bce27",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => response.data)
|
||||||
|
.then((data) => {
|
||||||
|
setSvgData(data?.plot?.replace('\"', '').replace('\n', '').replace('"', "'"));
|
||||||
|
setLoading(false); // Set loading to false once data is fetched
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
setLoading(false); // Set loading to false in case of an error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!svgData) {
|
||||||
|
fetchSvg();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Plots</h1>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="spinner-container">
|
||||||
|
{/* Add your spinner component here or use a simple text */}
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<img src={`https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/crdboxplot`} width="450" height="340" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <ImageDownloader /> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CurveFit;
|
||||||
@@ -24,7 +24,7 @@ const Devices = () => {
|
|||||||
setTestDate(null);
|
setTestDate(null);
|
||||||
setCurTest(null);
|
setCurTest(null);
|
||||||
setDays([...new Set(allTests
|
setDays([...new Set(allTests
|
||||||
.filter((x) => x.deviceSerialNumber === value)
|
.filter((x) => x.deviceSerialNumber === value || x.deviceId === value)
|
||||||
.map(x => moment(x.testTime).format("YYYY-MM-DD")))]
|
.map(x => moment(x.testTime).format("YYYY-MM-DD")))]
|
||||||
.sort()
|
.sort()
|
||||||
.reverse());
|
.reverse());
|
||||||
@@ -36,7 +36,7 @@ const Devices = () => {
|
|||||||
setCurTestData(null);
|
setCurTestData(null);
|
||||||
setShowGraph(false);
|
setShowGraph(false);
|
||||||
setTests(allTests
|
setTests(allTests
|
||||||
.filter((x) => x.deviceSerialNumber === device && moment(x.testTime).isBetween(moment(value).startOf('day'), moment(value).endOf('day')))
|
.filter((x) => (x.deviceSerialNumber === device || x.deviceId === device) && moment(x.testTime).isBetween(moment(value).startOf('day'), moment(value).endOf('day')))
|
||||||
.map(x => x._id)
|
.map(x => x._id)
|
||||||
.sort());
|
.sort());
|
||||||
}
|
}
|
||||||
@@ -70,8 +70,13 @@ const Devices = () => {
|
|||||||
querySnapshot.forEach((document) => {
|
querySnapshot.forEach((document) => {
|
||||||
const hposTestData = document.data();
|
const hposTestData = document.data();
|
||||||
hposTests.push(hposTestData);
|
hposTests.push(hposTestData);
|
||||||
const { deviceSerialNumber } = hposTestData;
|
const { deviceType, deviceId, deviceSerialNumber } = hposTestData;
|
||||||
testDevices.add(deviceSerialNumber);
|
if (deviceType === 'HEMOCUBE' && deviceId) {
|
||||||
|
testDevices.add(deviceId);
|
||||||
|
}
|
||||||
|
if (deviceType !== 'HEMOCUBE' && deviceSerialNumber) {
|
||||||
|
testDevices.add(deviceSerialNumber);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const groups = [...testDevices];
|
const groups = [...testDevices];
|
||||||
setDevices(groups);
|
setDevices(groups);
|
||||||
|
|||||||
0
src/components/Diagnostics.css
Normal file
68
src/components/Diagnostics.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import { query, collection, onSnapshot, where, orderBy, limit } from 'firebase/firestore';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import db from '../firebase';
|
||||||
|
import './Diagnostics.css';
|
||||||
|
|
||||||
|
const Diagnostics = () => {
|
||||||
|
const [samples, setSamples] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!samples.length) {
|
||||||
|
const q = query(collection(db, "diagnostics"), where("runTime", ">=", moment().format("YYYY-MM-DD").toString()), orderBy("runTime", "desc"));
|
||||||
|
|
||||||
|
const unsub = onSnapshot(q, (querySnapshot) => {
|
||||||
|
const samples = [];
|
||||||
|
querySnapshot.forEach((document) => {
|
||||||
|
console.log(document.id);
|
||||||
|
const data = document.data();
|
||||||
|
const id = document.id;
|
||||||
|
samples.push({id, ...data});
|
||||||
|
});
|
||||||
|
setSamples(samples);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => unsub;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* <div className='samples-heading'><h3>Samples page for denovix useage</h3></div>
|
||||||
|
{samples.length ? (
|
||||||
|
samples.map((alert, index) => (
|
||||||
|
<div key={index} className='samples-card'>
|
||||||
|
<div tabIndex={index}><strong>{alert._id}</strong></div>
|
||||||
|
<p className='sample-time'>{alert.testTime}</p>
|
||||||
|
<p className='sample-time-ago'>{moment(alert.testTime).fromNow()}</p>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className='no-samples-message'>Samples will appear as in when a test is recorded</div>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
|
||||||
|
<h1>Device Data</h1>
|
||||||
|
|
||||||
|
<div className='samples-container'>
|
||||||
|
<table id="samples-table">
|
||||||
|
<tr>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Raw Data</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
{samples.map((sample, index) => (
|
||||||
|
<tr key={index} className='samples-table-tr'>
|
||||||
|
<td>{sample.deviceId}</td>
|
||||||
|
<td>{sample.deviceData || 'NA'}</td>
|
||||||
|
<td>{moment(sample.runTime).format('HH:mm:ss')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Diagnostics;
|
||||||
29
src/components/ExportData.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
.button {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
/* Green */
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
/* padding: 15px 32px; */
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dates {
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-date {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-date {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
32
src/components/ExportData.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import DatePicker from "react-datepicker";
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
|
|
||||||
|
import './ExportData.css';
|
||||||
|
|
||||||
|
function ExportData({ url, show }) {
|
||||||
|
const [startDate, setStartDate] = useState("");
|
||||||
|
const [endDate, setEndDate] = useState("");
|
||||||
|
|
||||||
|
const fetchFile = (start_date, end_date) => {
|
||||||
|
window.location.href = `https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/consolidation?start_date=${moment(startDate).format("YYYY-MM-DD")}&end_date=${moment(endDate).format("YYYY-MM-DD")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='dates'>
|
||||||
|
<div className='start-date'>
|
||||||
|
<DatePicker selected={startDate} onChange={(date) => setStartDate(date)} placeholderText='start date' />
|
||||||
|
</div>
|
||||||
|
<div className='end-date'>
|
||||||
|
<DatePicker selected={endDate} onChange={(date) => setEndDate(date)} placeholderText='end date' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='button' onClick={() => fetchFile(startDate, endDate)}>Download Excel</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default ExportData;
|
||||||
103
src/components/Gemini.css
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
.user-chat-input {
|
||||||
|
margin: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message {
|
||||||
|
background-color: #aaffaa;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-message {
|
||||||
|
background-color: #aaaaff;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-animation {
|
||||||
|
overflow: hidden; /* Hide overflowing text */
|
||||||
|
border-right: 0.1em solid #000; /* Add a border to simulate the cursor */
|
||||||
|
white-space: nowrap; /* Ensure the text stays in one line */
|
||||||
|
animation: typing 1s steps(40, end), blink-caret 0.5s step-end infinite; /* Typing animation */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes typing {
|
||||||
|
from {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-caret {
|
||||||
|
from, to {
|
||||||
|
border-color: transparent; /* Blinking cursor effect */
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply basic styles to the table */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add styling to the table header */
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the table header */
|
||||||
|
th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add some hover effect to the table rows */
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
/* background-color: #f5f5f5; */
|
||||||
|
/* border: 1px solid #ddd; */
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
239
src/components/Gemini.js
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { query, collection, onSnapshot, addDoc, where, getDocs, orderBy, getCountFromServer } from 'firebase/firestore';
|
||||||
|
import Markdown from 'react-markdown';
|
||||||
|
import MathJax from 'react-mathjax';
|
||||||
|
import RemarkMathPlugin from 'remark-math';
|
||||||
|
import remarkGfm from 'remark-gfm';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { FaSpinner } from 'react-icons/fa';
|
||||||
|
// import { MathJax, MathJaxContext } from "better-react-mathjax";
|
||||||
|
|
||||||
|
import './Gemini.css'; // Make sure to have your CSS styles for the typewriter effect
|
||||||
|
import db from '../firebase';
|
||||||
|
|
||||||
|
import 'katex/dist/katex.min.css';
|
||||||
|
|
||||||
|
function Gemini(props) {
|
||||||
|
const [messages, setMessages] = useState([]);
|
||||||
|
const [newMessage, setNewMessage] = useState('');
|
||||||
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
|
const [discussionId, setDiscussionId] = useState(uuidv4());
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [chatCount, setChatCount] = useState(0);
|
||||||
|
|
||||||
|
const scrollRef = useRef(null);
|
||||||
|
|
||||||
|
const newProps = {
|
||||||
|
...props,
|
||||||
|
options: {
|
||||||
|
...props.options,
|
||||||
|
inlineMath: [['$', '$'], ['\\(', '\\)']], // Adjust delimiters if needed
|
||||||
|
displayMath: [['$$', '$$'], ['\\[', '\\]']], // Adjust delimiters if needed
|
||||||
|
},
|
||||||
|
remarkPlugins: [
|
||||||
|
RemarkMathPlugin,
|
||||||
|
remarkGfm,
|
||||||
|
],
|
||||||
|
// renderers: {
|
||||||
|
// ...props.renderers,
|
||||||
|
// math: (props) =>
|
||||||
|
// <MathJax.Node formula={props.value} />,
|
||||||
|
// inlineMath: (props) =>
|
||||||
|
// <MathJax.Node inline formula={props.value} />
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
const newComponents = {
|
||||||
|
...props.components,
|
||||||
|
math: (props) => (
|
||||||
|
<MathJax.Node formula={props.value} />
|
||||||
|
),
|
||||||
|
inlineMath: (props) => (
|
||||||
|
<MathJax.Node inline formula={props.value} />
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDocumentCount = async (uid) => {
|
||||||
|
const collectionPath = `users/uid/discussions`;
|
||||||
|
try {
|
||||||
|
const querySnapshot = await getCountFromServer(collection(db, collectionPath));
|
||||||
|
// const documentCount = querySnapshot.size;
|
||||||
|
// setChatCount(querySnapshot);
|
||||||
|
console.log(`Document count in the collection "${collectionPath}": ${JSON.stringify(querySnapshot._data.count.integerValue)}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting document count:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadChatHistory = async () => {
|
||||||
|
const uid = 'uid'; // Replace with your user ID
|
||||||
|
// const discussionId = uuidv4();
|
||||||
|
console.log(discussionId)
|
||||||
|
|
||||||
|
getDocumentCount(uid);
|
||||||
|
|
||||||
|
const collectionPath = `users/${uid}/discussions/${discussionId}/messages`;
|
||||||
|
|
||||||
|
const q = query(collection(db, collectionPath), orderBy('createTime'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const querySnapshot = await getDocs(q);
|
||||||
|
const loadedMessages = [];
|
||||||
|
|
||||||
|
querySnapshot.forEach((doc) => {
|
||||||
|
const data = doc.data();
|
||||||
|
loadedMessages.push({ text: data.prompt, sender: 'user' });
|
||||||
|
|
||||||
|
if (data.response) {
|
||||||
|
// console.log(data.response)
|
||||||
|
// Simulate delay for typewriter effect
|
||||||
|
// setTimeout(() => {
|
||||||
|
// // Update the state with the Gemini's response
|
||||||
|
// setMessages((prevMessages) => [
|
||||||
|
// ...prevMessages,
|
||||||
|
// { text: data.response, sender: 'gemini' },
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// // if (scrollRef.current) {
|
||||||
|
// // // alert(scrollRef.current.scrollHeight)
|
||||||
|
// // scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
|
// // }
|
||||||
|
// }, 10); // You can adjust the delay as needed
|
||||||
|
|
||||||
|
|
||||||
|
loadedMessages.push({ text: data.response, sender: 'gemini' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort the messages by createTime before updating the state
|
||||||
|
// loadedMessages.sort((a, b) => a.createTime - b.createTime);
|
||||||
|
setMessages(loadedMessages);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading chat history:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
// alert(scrollRef.current.scrollHeight)
|
||||||
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadChatHistory();
|
||||||
|
}, []); // Empty dependency array ensures the effect runs only once on component mount
|
||||||
|
|
||||||
|
const handleSendMessage = async () => {
|
||||||
|
if (newMessage.trim() === '') return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const uid = 'uid'; // Replace with your user ID
|
||||||
|
|
||||||
|
const collectionPath = `users/${uid}/discussions/${discussionId}/messages`;
|
||||||
|
const collectionRef = collection(db, collectionPath);
|
||||||
|
|
||||||
|
// Add user's message to the local state first
|
||||||
|
setMessages([...messages, { text: newMessage, sender: 'user' }]);
|
||||||
|
setNewMessage('');
|
||||||
|
|
||||||
|
// Then, add the user's message to the Firestore collection
|
||||||
|
// Your existing code for adding user's message remains unchanged
|
||||||
|
|
||||||
|
const docRef = await addDoc(collectionRef, {
|
||||||
|
prompt: newMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for changes in the Firestore document
|
||||||
|
const unsub = onSnapshot(docRef, (doc) => {
|
||||||
|
const data = doc.data();
|
||||||
|
if (data && data.response) {
|
||||||
|
// console.log(data.response)
|
||||||
|
// Start typewriter animation
|
||||||
|
// setIsTyping(true);
|
||||||
|
|
||||||
|
// Simulate delay for typewriter effect
|
||||||
|
setTimeout(() => {
|
||||||
|
// Update the state with the Gemini's response
|
||||||
|
setMessages((prevMessages) => [...prevMessages, { text: data.response, sender: 'gemini' }]);
|
||||||
|
// Stop typewriter animation
|
||||||
|
// setIsTyping(false);
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
}, 200); // You can adjust the delay as needed
|
||||||
|
}
|
||||||
|
// Scroll to the bottom
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unsub;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = debounce((value) => {
|
||||||
|
setNewMessage(value);
|
||||||
|
}, 30); // Adjust the delay as needed
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* <div>Chats: {chatCount}</div> */}
|
||||||
|
<div style={{ height: '500px', overflowY: 'scroll', border: '1px solid #ccc' }} ref={scrollRef}>
|
||||||
|
{messages.map((message, index) => (
|
||||||
|
<div key={index} className={message.sender === 'user' ? 'user-message' : 'other-message'}>
|
||||||
|
{message.sender === 'gemini' && isTyping ? (
|
||||||
|
<div className="typing-animation">Gemini is typing...</div>
|
||||||
|
) : (
|
||||||
|
<MathJax.Provider input="tex">
|
||||||
|
<Markdown components={newComponents} {...newProps}>{message.text}</Markdown>
|
||||||
|
</MathJax.Provider>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="user-chat-input">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newMessage}
|
||||||
|
onChange={(e) => setNewMessage(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSendMessage();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="Type your message..."
|
||||||
|
style={{ marginRight: '8px', padding: '8px', fontSize: '16px', width: '50vw' }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#4CAF50',
|
||||||
|
color: 'white',
|
||||||
|
padding: '10px 20px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
{isLoading && <div className='loading-container'>
|
||||||
|
<div className="loading-icon">
|
||||||
|
<FaSpinner className="spin" />
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Gemini;
|
||||||
19
src/components/Help.css
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.step-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 10px;
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-image {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-description {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-feature {
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
128
src/components/Help.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import Accordion from '@mui/material/Accordion';
|
||||||
|
import AccordionDetails from '@mui/material/AccordionDetails';
|
||||||
|
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
|
||||||
|
import './Help.css';
|
||||||
|
|
||||||
|
function Help({ url, show }) {
|
||||||
|
const [startDate, setStartDate] = useState("");
|
||||||
|
const [endDate, setEndDate] = useState("");
|
||||||
|
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
const handleChange =
|
||||||
|
(panel) => (event, isExpanded) => {
|
||||||
|
setExpanded(isExpanded ? panel : false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Accordion expanded={expanded === 'panel1'} onChange={handleChange('panel1')}>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls="panel1bh-content"
|
||||||
|
id="panel1bh-header"
|
||||||
|
>
|
||||||
|
<Typography sx={{ width: '33%', flexShrink: 0 }}>
|
||||||
|
Auto Analysis
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ color: 'text.secondary' }}>Auto Analysis of device data with dye and HB</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography>
|
||||||
|
<div className='step-title'>Step 1:</div>
|
||||||
|
<div className='step-description'>Click on the start date tab and select the necessary date. Repeat the same process on the end date tab as well.</div>
|
||||||
|
<img className="help-image" src={require('../assets/auto_analysis_help-1.png')} alt='icon'></img>
|
||||||
|
|
||||||
|
<div className='step-title'>Step 2:</div>
|
||||||
|
<div className='step-description'>Select an end date, it has to be at least one date post the start date.</div>
|
||||||
|
<img className="help-image" src={require('../assets/auto_analysis_help-2.png')} alt='icon'></img>
|
||||||
|
|
||||||
|
<div className='step-title'>Step 3:</div>
|
||||||
|
<div className='step-description'>Click on the Download Excel button and wait for the document to download.</div>
|
||||||
|
<img className="help-image" src={require('../assets/auto_analysis_help-3.png')} alt='icon'></img>
|
||||||
|
</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
|
||||||
|
<Accordion expanded={expanded === 'panel2'} onChange={handleChange('panel2')}>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls="panel2bh-content"
|
||||||
|
id="panel2bh-header"
|
||||||
|
>
|
||||||
|
<Typography sx={{ width: '33%', flexShrink: 0 }}>Raw Data</Typography>
|
||||||
|
<Typography sx={{ color: 'text.secondary' }}>
|
||||||
|
Download raw data
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography>
|
||||||
|
<div className='step-title'>Step 1:</div>
|
||||||
|
<div className='step-description'>Click on the <strong>start date</strong> tab and select the necessary date. Repeat the same process on the <strong>end date</strong> tab as well.</div>
|
||||||
|
<img className="help-image" src={require('../assets/raw_data_help_1.png')} alt='icon'></img>
|
||||||
|
<img className="help-image" src={require('../assets/raw_data_help_2.png')} alt='icon'></img>
|
||||||
|
|
||||||
|
<div className='step-title'>Step 2:</div>
|
||||||
|
<div className='step-description'>Click on the <strong>Download Excel</strong> button and wait for the document to download.</div>
|
||||||
|
<img className="help-image" src={require('../assets/raw_data_help_3.png')} alt='icon'></img>
|
||||||
|
</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
|
||||||
|
<Accordion expanded={expanded === 'panel3'} onChange={handleChange('panel3')}>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls="panel3bh-content"
|
||||||
|
id="panel3bh-header"
|
||||||
|
>
|
||||||
|
<Typography sx={{ width: '33%', flexShrink: 0 }}>
|
||||||
|
Registration
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ color: 'text.secondary' }}>
|
||||||
|
Create and clear entries for taking readings in devices
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography>
|
||||||
|
|
||||||
|
<div className='step-title'>Step 1:</div>
|
||||||
|
<div className='step-description'>Enter the sample or solution name in the <strong>name</strong> tab with appropriate format given below.</div>
|
||||||
|
<img className="help-image" src={require('../assets/registration_help_1.png')} alt='icon'></img>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='step-title'>Step 2:</div>
|
||||||
|
<div className='step-description'>Click on the create button to get an ID and you will receive a confirmation message.
|
||||||
|
|
||||||
|
If you wish to clear the generated IDs click on the clear button and you will receive a confirmation message.</div>
|
||||||
|
<img className="help-image" src={require('../assets/raw_data_help_2.png')} alt='icon'></img>
|
||||||
|
<img className="help-image" src={require('../assets/raw_data_help_3.png')} alt='icon'></img>
|
||||||
|
</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
|
||||||
|
{/* <Accordion expanded={expanded === 'panel4'} onChange={handleChange('panel4')}>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls="panel4bh-content"
|
||||||
|
id="panel4bh-header"
|
||||||
|
>
|
||||||
|
<Typography sx={{ width: '33%', flexShrink: 0 }}>Personal data</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography>
|
||||||
|
Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit
|
||||||
|
amet egestas eros, vitae egestas augue. Duis vel est augue.
|
||||||
|
</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Help;
|
||||||
9
src/components/HomePage.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const HomePage = (props) => {
|
||||||
|
return (
|
||||||
|
<div>HomePage</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
47
src/components/ImageDownloader.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const ImageDownloader = () => {
|
||||||
|
const [imageUrl, setImageUrl] = useState('');
|
||||||
|
const [imageData, setImageData] = useState(null);
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
setImageUrl(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFetchImage = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(imageUrl, { mode: 'no-cors'});
|
||||||
|
const blob = await response.blob();
|
||||||
|
console.log(blob);
|
||||||
|
setImageData(URL.createObjectURL(blob));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching image:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownloadImage = () => {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = imageData;
|
||||||
|
link.download = 'downloaded_image';
|
||||||
|
link.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
Image URL:
|
||||||
|
<input type="text" value={imageUrl} onChange={handleInputChange} />
|
||||||
|
</label>
|
||||||
|
<button onClick={handleFetchImage}>Fetch Image</button>
|
||||||
|
|
||||||
|
{imageData && (
|
||||||
|
<div>
|
||||||
|
<img src={imageData} alt="Downloadable" style={{ maxWidth: '100%' }} />
|
||||||
|
<button onClick={handleDownloadImage}>Download Image</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageDownloader;
|
||||||
0
src/components/KitCheck.css
Normal file
70
src/components/KitCheck.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import { query, collection, onSnapshot, where, orderBy, limit } from 'firebase/firestore';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import db from '../firebase';
|
||||||
|
import './KitCheck.css';
|
||||||
|
|
||||||
|
const KitCheck = () => {
|
||||||
|
const [samples, setSamples] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!samples.length) {
|
||||||
|
const q = query(collection(db, "buffers"), where("runTime", ">=", moment().format("YYYY-MM-DD").toString()), orderBy("runTime", "desc"));
|
||||||
|
|
||||||
|
const unsub = onSnapshot(q, (querySnapshot) => {
|
||||||
|
const samples = [];
|
||||||
|
querySnapshot.forEach((document) => {
|
||||||
|
console.log(document.id);
|
||||||
|
const data = document.data();
|
||||||
|
const id = document.id;
|
||||||
|
samples.push({id, ...data});
|
||||||
|
});
|
||||||
|
setSamples(samples);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => unsub;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* <div className='samples-heading'><h3>Samples page for denovix useage</h3></div>
|
||||||
|
{samples.length ? (
|
||||||
|
samples.map((alert, index) => (
|
||||||
|
<div key={index} className='samples-card'>
|
||||||
|
<div tabIndex={index}><strong>{alert._id}</strong></div>
|
||||||
|
<p className='sample-time'>{alert.testTime}</p>
|
||||||
|
<p className='sample-time-ago'>{moment(alert.testTime).fromNow()}</p>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className='no-samples-message'>Samples will appear as in when a test is recorded</div>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
|
||||||
|
<h1>Kit Check</h1>
|
||||||
|
|
||||||
|
<div className='samples-container'>
|
||||||
|
<table id="samples-table">
|
||||||
|
<tr>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Kit</th>
|
||||||
|
<th>Result</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
{samples.map((sample, index) => (
|
||||||
|
<tr key={index} className='samples-table-tr'>
|
||||||
|
<td>{sample.deviceId}</td>
|
||||||
|
<td>{sample.kitSerial || 'NA'}</td>
|
||||||
|
<td>{sample.classificationResult || 'NA'}</td>
|
||||||
|
<td>{moment(sample.runTime).format('HH:mm:ss')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KitCheck;
|
||||||
38
src/components/MainLayout.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import { Box, Toolbar } from "@mui/material";
|
||||||
|
import colorConfigs from "../configs/colorConfigs";
|
||||||
|
import sizeConfigs from "../configs/sizeConfigs";
|
||||||
|
import Sidebar from "./nav/Sidebar";
|
||||||
|
import Topbar from "./nav/Topbar";
|
||||||
|
|
||||||
|
const MainLayout = () => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<Topbar />
|
||||||
|
<Box
|
||||||
|
component="nav"
|
||||||
|
sx={{
|
||||||
|
width: sizeConfigs.sidebar.width,
|
||||||
|
flexShrink: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sidebar />
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
component="main"
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
p: 3,
|
||||||
|
width: `calc(100% - ${sizeConfigs.sidebar.width})`,
|
||||||
|
minHeight: "100vh",
|
||||||
|
backgroundColor: colorConfigs.mainBg
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Toolbar />
|
||||||
|
<Outlet />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainLayout;
|
||||||
19
src/components/PageWrapper.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { setAppState } from "../store/appStateSlice";
|
||||||
|
|
||||||
|
const PageWrapper = (props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.state) {
|
||||||
|
dispatch(setAppState(props.state));
|
||||||
|
}
|
||||||
|
}, [dispatch, props]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>{props.children}</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageWrapper;
|
||||||
0
src/components/Precision.css
Normal file
15
src/components/Precision.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FaInfoCircle } from "react-icons/fa";
|
||||||
|
|
||||||
|
import './Precision.css';
|
||||||
|
|
||||||
|
function Precision({ show }) {
|
||||||
|
const [showCardInfo, setShowCardInfo] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Precision</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Precision;
|
||||||
32
src/components/Registration.css
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 80vw;
|
||||||
|
height: 50px;
|
||||||
|
background-color: rgb(187, 218, 174);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-creation {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-users-button {
|
||||||
|
background-color: #b62200;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.format-text {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
107
src/components/Registration.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import './Registration.css';
|
||||||
|
|
||||||
|
function Registration() {
|
||||||
|
const [userId, setUserId] = useState("2808202300103SMIBLR");
|
||||||
|
const [counter, setCounter] = useState(0);
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
|
||||||
|
const createUser = () => {
|
||||||
|
setMessage("");
|
||||||
|
const id = moment().unix() + counter + "SMIBLR";
|
||||||
|
setCounter(counter + 1);
|
||||||
|
axios({
|
||||||
|
method: 'post',
|
||||||
|
url: `https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/registerUser`,
|
||||||
|
data: {
|
||||||
|
"_id": id,
|
||||||
|
"aadharId": "2808202300001",
|
||||||
|
"abhaId": "12434949797979",
|
||||||
|
"birthYear": "2000",
|
||||||
|
"bloodGroup": "",
|
||||||
|
"careOf": "",
|
||||||
|
"caste": "",
|
||||||
|
"city": "Bengaluru",
|
||||||
|
"district": "Bengaluru",
|
||||||
|
"gender": "Male",
|
||||||
|
"house": "42",
|
||||||
|
"maritalStatus": "Single",
|
||||||
|
"name": name ? name : "Web",
|
||||||
|
"phoneNumber": "9123456789",
|
||||||
|
"pinCode": "560060",
|
||||||
|
"registeredBy": "admin",
|
||||||
|
"registrationCenterName": "SMI",
|
||||||
|
"sickleCellHistory": null,
|
||||||
|
"state": "Karnataka",
|
||||||
|
"subCaste": "",
|
||||||
|
"testStatus": false,
|
||||||
|
"incubationTime": moment().subtract('15', 'minutes').utcOffset("+05:30").format("YYYY-MM-DD HH:mm:ss"),
|
||||||
|
"underMedication": false,
|
||||||
|
"underTransfusion": false,
|
||||||
|
"userImageURL": "https://hpos-prod.web.app/logo.png"
|
||||||
|
},
|
||||||
|
withCredentials: false,
|
||||||
|
secure: false,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"x-client": "e16a1bd15af2ee640f5a7a18c8d8333f5f01f7c66e003bbc21ee52870f1bce27",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => response.data)
|
||||||
|
.then((data) => {
|
||||||
|
setMessage(`${id} is being created`);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
setMessage(`error: ${err?.message}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearsUsers = () => {
|
||||||
|
setMessage("");
|
||||||
|
axios({
|
||||||
|
method: 'get',
|
||||||
|
url: `https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/clearUsers`,
|
||||||
|
withCredentials: false,
|
||||||
|
secure: false,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
// "x-client": "e16a1bd15af2ee640f5a7a18c8d8333f5f01f7c66e003bbc21ee52870f1bce27",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => response.data)
|
||||||
|
.then((data) => {
|
||||||
|
setMessage(`${data}`);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
setMessage(`error: ${err?.message}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const onNameChange = (event) => {
|
||||||
|
setName(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="user-creation">
|
||||||
|
<label>
|
||||||
|
name:
|
||||||
|
<input name="name" value={name} placeholder="350 UMOL KMNO4" onChange={onNameChange} />
|
||||||
|
</label>
|
||||||
|
<h5 className='format-text'>Correct formats for automatic analysis are: 100 UMOL KMNO4, 290uM KMnO4, KMnO4-720umol, 350 UMOL TAR, Tar-120umol, HB-13-N-4, HB-11-4</h5>
|
||||||
|
<div className='dates'>
|
||||||
|
<div className='button' onClick={() => createUser()}>Create</div>
|
||||||
|
</div>
|
||||||
|
<div className='dates'>
|
||||||
|
<div className='clear-users-button' onClick={() => clearsUsers()}>Clear</div>
|
||||||
|
</div>
|
||||||
|
<div>{message && <div className='message'>{message}</div>}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Registration;
|
||||||
39
src/components/Samples.css
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#samples-table {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
#samples-table td,
|
||||||
|
#samples-table th {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
width: 10vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#samples-table td {
|
||||||
|
width: 15vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#samples-table tr:nth-child(even) {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#samples-table tr:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#samples-table th {
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgb(144, 183, 255);
|
||||||
|
color: black;
|
||||||
|
width: 5vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.samples-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
87
src/components/Samples.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import { query, collection, onSnapshot, where, orderBy, limit } from 'firebase/firestore';
|
||||||
|
|
||||||
|
import './Samples.css';
|
||||||
|
import db from '../firebase';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const Samples = () => {
|
||||||
|
const [samples, setSamples] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!samples.length) {
|
||||||
|
const q = query(collection(db, "testData"), where("testTime", ">=", moment().format("YYYY-MM-DD").toString()), orderBy("testTime", "desc"));
|
||||||
|
|
||||||
|
const unsub = onSnapshot(q, (querySnapshot) => {
|
||||||
|
const samples = [];
|
||||||
|
querySnapshot.forEach((document) => {
|
||||||
|
console.log(document.id);
|
||||||
|
const data = document.data();
|
||||||
|
const id = document.id;
|
||||||
|
samples.push({id, ...data});
|
||||||
|
});
|
||||||
|
setSamples(samples);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => unsub;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* <div className='samples-heading'><h3>Samples page for denovix useage</h3></div>
|
||||||
|
{samples.length ? (
|
||||||
|
samples.map((alert, index) => (
|
||||||
|
<div key={index} className='samples-card'>
|
||||||
|
<div tabIndex={index}><strong>{alert._id}</strong></div>
|
||||||
|
<p className='sample-time'>{alert.testTime}</p>
|
||||||
|
<p className='sample-time-ago'>{moment(alert.testTime).fromNow()}</p>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className='no-samples-message'>Samples will appear as in when a test is recorded</div>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
|
||||||
|
<h1>Samples</h1>
|
||||||
|
|
||||||
|
<div className='samples-container'>
|
||||||
|
<table id="samples-table">
|
||||||
|
<tr>
|
||||||
|
<th>Sample Name</th>
|
||||||
|
<th>CResult</th>
|
||||||
|
<th>Dev Ratio Class</th>
|
||||||
|
<th>BM2 Class</th>
|
||||||
|
<th>Dev Ratio</th>
|
||||||
|
<th>Slope Ratio</th>
|
||||||
|
<th>led1Sam.</th>
|
||||||
|
{/* <th>led2Average</th> */}
|
||||||
|
<th>led2Sam.</th>
|
||||||
|
<th>led3Sam.</th>
|
||||||
|
<th>led4Sam.</th>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
{samples.map((sample, index) => (
|
||||||
|
<tr key={index} className='samples-table-tr'>
|
||||||
|
<td>{sample.name || sample._id}</td>
|
||||||
|
<td>{sample.classificationResult}</td>
|
||||||
|
<td>{sample.deviceRatioClass}</td>
|
||||||
|
<td>{sample.borderlineMethod2Class}</td>
|
||||||
|
<td>{sample?.deviceRatio?.toFixed(4)}</td>
|
||||||
|
<td>{sample?.slopeRatio?.toFixed(4)}</td>
|
||||||
|
<td>{sample.led1Sample || 'NA'}</td>
|
||||||
|
<td>{sample.led2Sample || 'NA'}</td>
|
||||||
|
<td>{sample.led3Sample || 'NA'}</td>
|
||||||
|
<td>{sample.led4Sample || 'NA'}</td>
|
||||||
|
<td>{sample.deviceId}</td>
|
||||||
|
<td>{moment(sample.testTime).format('HH:mm:ss')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Samples;
|
||||||
@@ -53,7 +53,10 @@ const TestAnalytics = () => {
|
|||||||
const dailyCountsList = Object.entries(dailyCounts).sort().map(x => {
|
const dailyCountsList = Object.entries(dailyCounts).sort().map(x => {
|
||||||
return { date: x[0], count: x[1] };
|
return { date: x[0], count: x[1] };
|
||||||
});
|
});
|
||||||
setDailyResults(dailyCountsList);
|
if (dailyCountsList?.length > 7)
|
||||||
|
setDailyResults(dailyCountsList.slice(dailyCountsList.length - 7, dailyCountsList.length));
|
||||||
|
else
|
||||||
|
setDailyResults(dailyCountsList);
|
||||||
const rss = _.groupBy(hposTests, function (test) {
|
const rss = _.groupBy(hposTests, function (test) {
|
||||||
return moment(test.testTime).startOf('day').format();
|
return moment(test.testTime).startOf('day').format();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ class TestAnalyticsGraph extends Component {
|
|||||||
|
|
||||||
data.reverse();
|
data.reverse();
|
||||||
|
|
||||||
var margin = { top: 60, right: 20, bottom: 30, left: 50 },
|
var margin = { top: 30, right: 20, bottom: 50, left: 15 },
|
||||||
width = 520 - margin.left - margin.right,
|
width = 400 - margin.left - margin.right,
|
||||||
height = 220 - margin.top - margin.bottom;
|
height = 180 - margin.top - margin.bottom;
|
||||||
|
|
||||||
var svg = d3.select("#tests-count-graph").append('svg')
|
var svg = d3.select("#tests-count-graph").append('svg')
|
||||||
.attr("width", width + margin.left + margin.right)
|
.attr("width", width + margin.left + margin.right)
|
||||||
@@ -113,7 +113,7 @@ class TestAnalyticsGraph extends Component {
|
|||||||
drawChart(data) {
|
drawChart(data) {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
|
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
|
||||||
width = 960 - margin.left - margin.right,
|
width = 420 - margin.left - margin.right,
|
||||||
height = 500 - margin.top - margin.bottom;
|
height = 500 - margin.top - margin.bottom;
|
||||||
|
|
||||||
const INNER_WIDTH = width;// - margin.left - margin.right;
|
const INNER_WIDTH = width;// - margin.left - margin.right;
|
||||||
|
|||||||
50
src/components/nav/Sidebar.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Avatar, Drawer, List, Stack, Toolbar } from "@mui/material";
|
||||||
|
|
||||||
|
// import assets from "../../assets";
|
||||||
|
import colorConfigs from "../../configs/colorConfigs";
|
||||||
|
import sizeConfigs from "../../configs/sizeConfigs";
|
||||||
|
import appRoutes from "../../routes/appRoutes";
|
||||||
|
import SidebarItem from "./SidebarItem";
|
||||||
|
import SidebarItemCollapse from "./SidebarItemCollapse";
|
||||||
|
|
||||||
|
const Sidebar = () => {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
variant="permanent"
|
||||||
|
sx={{
|
||||||
|
width: sizeConfigs.sidebar.width,
|
||||||
|
flexShrink: 0,
|
||||||
|
"& .MuiDrawer-paper": {
|
||||||
|
width: sizeConfigs.sidebar.width,
|
||||||
|
boxSizing: "border-box",
|
||||||
|
borderRight: "0px",
|
||||||
|
backgroundColor: colorConfigs.sidebar.bg,
|
||||||
|
color: colorConfigs.sidebar.color
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<List disablePadding>
|
||||||
|
<Toolbar sx={{ marginBottom: "20px" }}>
|
||||||
|
<Stack
|
||||||
|
sx={{ width: "100%" }}
|
||||||
|
direction="row"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Avatar src={require('../../smi-logo-120x120.webp')} />
|
||||||
|
</Stack>
|
||||||
|
</Toolbar>
|
||||||
|
{appRoutes.map((route, index) => (
|
||||||
|
route.sidebarProps ? (
|
||||||
|
route.child ? (
|
||||||
|
<SidebarItemCollapse item={route} key={index} />
|
||||||
|
) : (
|
||||||
|
<SidebarItem item={route} key={index} />
|
||||||
|
)
|
||||||
|
) : null
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
||||||
34
src/components/nav/SidebarItem.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ListItemButton, ListItemIcon } from "@mui/material";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import colorConfigs from "../../configs/colorConfigs";
|
||||||
|
|
||||||
|
const SidebarItem = ({ item }) => {
|
||||||
|
const { appState } = useSelector((state) => state.appState);
|
||||||
|
|
||||||
|
return (
|
||||||
|
item.sidebarProps && item.path ? (
|
||||||
|
<ListItemButton
|
||||||
|
component={Link}
|
||||||
|
to={item.path}
|
||||||
|
sx={{
|
||||||
|
"&: hover": {
|
||||||
|
backgroundColor: colorConfigs.sidebar.hoverBg
|
||||||
|
},
|
||||||
|
backgroundColor: appState === item.state ? colorConfigs.sidebar.activeBg : "unset",
|
||||||
|
paddingY: "12px",
|
||||||
|
paddingX: "24px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon sx={{
|
||||||
|
color: colorConfigs.sidebar.color
|
||||||
|
}}>
|
||||||
|
{item.sidebarProps.icon && item.sidebarProps.icon}
|
||||||
|
</ListItemIcon>
|
||||||
|
{item.sidebarProps.displayText}
|
||||||
|
</ListItemButton>
|
||||||
|
) : null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SidebarItem;
|
||||||
66
src/components/nav/SidebarItemCollapse.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { Collapse, List, ListItemButton, ListItemIcon, ListItemText, Typography } from "@mui/material";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import colorConfigs from "../../configs/colorConfigs";
|
||||||
|
import ExpandLessOutlinedIcon from '@mui/icons-material/ExpandLessOutlined';
|
||||||
|
import ExpandMoreOutlinedIcon from '@mui/icons-material/ExpandMoreOutlined';
|
||||||
|
import SidebarItem from "./SidebarItem";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
const SidebarItemCollapse = ({ item }) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const { appState } = useSelector((state) => state.appState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (appState.includes(item.state)) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}, [appState, item]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
item.sidebarProps ? (
|
||||||
|
<>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
sx={{
|
||||||
|
"&: hover": {
|
||||||
|
backgroundColor: colorConfigs.sidebar.hoverBg
|
||||||
|
},
|
||||||
|
paddingY: "12px",
|
||||||
|
paddingX: "24px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon sx={{
|
||||||
|
color: colorConfigs.sidebar.color
|
||||||
|
}}>
|
||||||
|
{item.sidebarProps.icon && item.sidebarProps.icon}
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
disableTypography
|
||||||
|
primary={
|
||||||
|
<Typography>
|
||||||
|
{item.sidebarProps.displayText}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{open ? <ExpandLessOutlinedIcon /> : <ExpandMoreOutlinedIcon />}
|
||||||
|
</ListItemButton>
|
||||||
|
<Collapse in={open} timeout="auto">
|
||||||
|
<List>
|
||||||
|
{item.child?.map((route, index) => (
|
||||||
|
route.sidebarProps ? (
|
||||||
|
route.child ? (
|
||||||
|
<SidebarItemCollapse item={route} key={index} />
|
||||||
|
) : (
|
||||||
|
<SidebarItem item={route} key={index} />
|
||||||
|
)
|
||||||
|
) : null
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
|
) : null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SidebarItemCollapse;
|
||||||
26
src/components/nav/Topbar.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { AppBar, Toolbar, Typography } from "@mui/material";
|
||||||
|
import colorConfigs from "../../configs/colorConfigs";
|
||||||
|
import sizeConfigs from "../../configs/sizeConfigs";
|
||||||
|
|
||||||
|
const Topbar = () => {
|
||||||
|
return (
|
||||||
|
<AppBar
|
||||||
|
position="fixed"
|
||||||
|
sx={{
|
||||||
|
width: `calc(100% - ${sizeConfigs.sidebar.width})`,
|
||||||
|
ml: sizeConfigs.sidebar.width,
|
||||||
|
boxShadow: "unset",
|
||||||
|
backgroundColor: colorConfigs.topbar.bg,
|
||||||
|
color: colorConfigs.topbar.color
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Toolbar>
|
||||||
|
<Typography variant="h6">
|
||||||
|
HPOS Web - {process.env.REACT_APP_TOPBAR_TITLE}
|
||||||
|
</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Topbar;
|
||||||
17
src/configs/colorConfigs.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { colors } from "@mui/material";
|
||||||
|
|
||||||
|
const colorConfigs = {
|
||||||
|
sidebar: {
|
||||||
|
bg: "#233044",
|
||||||
|
color: "#eeeeee",
|
||||||
|
hoverBg: "#1e293a",
|
||||||
|
activeBg: "#1e253a"
|
||||||
|
},
|
||||||
|
topbar: {
|
||||||
|
bg: "#fff",
|
||||||
|
color: "#000"
|
||||||
|
},
|
||||||
|
mainBg: colors.grey["100"]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default colorConfigs;
|
||||||
7
src/configs/sizeConfigs.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const sizeConfigs = {
|
||||||
|
sidebar: {
|
||||||
|
width: "260px"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sizeConfigs;
|
||||||
@@ -3,30 +3,16 @@ import { getFirestore} from "firebase/firestore";
|
|||||||
import { initializeApp } from "firebase/app";
|
import { initializeApp } from "firebase/app";
|
||||||
import { getAnalytics } from "firebase/analytics";
|
import { getAnalytics } from "firebase/analytics";
|
||||||
|
|
||||||
// /* dev and test */
|
const firebaseConfig = {
|
||||||
// const firebaseConfig = {
|
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
|
||||||
// apiKey: "AIzaSyBFl_YkJ5PMIvMY3G70313SyrqemjFnUv8",
|
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
|
||||||
// authDomain: "hpos-af3cc.firebaseapp.com",
|
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
|
||||||
// projectId: "hpos-af3cc",
|
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
|
||||||
// storageBucket: "hpos-af3cc.appspot.com",
|
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
|
||||||
// messagingSenderId: "650071678820",
|
appId: process.env.REACT_APP_FIREBASE_APP_ID,
|
||||||
// appId: "1:650071678820:web:ce50538bf46632d46c6471",
|
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
|
||||||
// measurementId: "G-6JHQ47S9DD"
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
|
||||||
/* prod */
|
|
||||||
const firebaseConfig = {
|
|
||||||
apiKey: "AIzaSyBF4pLZReAedU4dWX1eRJNFNIaOIz2xk4s",
|
|
||||||
authDomain: "hpos-prod.firebaseapp.com",
|
|
||||||
projectId: "hpos-prod",
|
|
||||||
storageBucket: "hpos-prod.appspot.com",
|
|
||||||
messagingSenderId: "1012719870714",
|
|
||||||
appId: "1:1012719870714:web:fcd00acef93f6612de5f43",
|
|
||||||
measurementId: "G-B0NZ7DEF69"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const app = initializeApp(firebaseConfig);
|
const app = initializeApp(firebaseConfig);
|
||||||
const analytics = getAnalytics(app);
|
const analytics = getAnalytics(app);
|
||||||
|
|
||||||
|
|||||||
14
src/index.js
@@ -4,6 +4,9 @@ import {
|
|||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
RouterProvider,
|
RouterProvider,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import { store } from './store/store';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
@@ -11,6 +14,7 @@ import Devices from './components/Devices';
|
|||||||
import TestAnalytics from './components/TestAnalytics';
|
import TestAnalytics from './components/TestAnalytics';
|
||||||
import WorldMap from './components/WorldMap';
|
import WorldMap from './components/WorldMap';
|
||||||
import RegistraionChart from './RegistraionChart';
|
import RegistraionChart from './RegistraionChart';
|
||||||
|
import TestAnalyticsGraph from './components/TestAnalyticsGraph';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -29,6 +33,10 @@ const router = createBrowserRouter([
|
|||||||
path: "/analytics",
|
path: "/analytics",
|
||||||
element: <TestAnalytics />,
|
element: <TestAnalytics />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/hposdata",
|
||||||
|
element: <TestAnalyticsGraph />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/map",
|
path: "/map",
|
||||||
element: <RegistraionChart />,
|
element: <RegistraionChart />,
|
||||||
@@ -38,7 +46,11 @@ const router = createBrowserRouter([
|
|||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
<RouterProvider router={router} />
|
<Provider store={store}>
|
||||||
|
{/* <CssBaseline /> */}
|
||||||
|
{/* <RouterProvider router={router} /> */}
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
|||||||
146
src/routes/appRoutes.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import DashboardOutlinedIcon from '@mui/icons-material/DashboardOutlined';
|
||||||
|
import AppsOutlinedIcon from '@mui/icons-material/AppsOutlined';
|
||||||
|
|
||||||
|
import TestAnalytics from "../components/TestAnalytics";
|
||||||
|
import HomePage from '../components/HomePage';
|
||||||
|
import Devices from '../components/Devices';
|
||||||
|
import { Download, FactCheck } from '@mui/icons-material';
|
||||||
|
import ExportData from '../components/ExportData';
|
||||||
|
import Registration from '../components/Registration';
|
||||||
|
import { FaAndroid, FaArrowCircleLeft, FaBars, FaBezierCurve, FaBox, FaChalkboardTeacher, FaChartArea, FaChartLine, FaCheck, FaCommentAlt, FaExclamation, FaFlask, FaGolfBall, FaMicrochip, FaPhone, FaQuestionCircle, FaSearch, FaUser } from 'react-icons/fa';
|
||||||
|
import AppInstallation from '../components/AppInstallation';
|
||||||
|
import Samples from '../components/Samples';
|
||||||
|
import Precision from '../components/Precision';
|
||||||
|
import Accuracy from '../components/Accuracy';
|
||||||
|
import CurveFit from '../components/CurveFit';
|
||||||
|
import Help from '../components/Help';
|
||||||
|
import Gemini from '../components/Gemini';
|
||||||
|
import Diagnostics from '../components/Diagnostics';
|
||||||
|
import KitCheck from '../components/KitCheck';
|
||||||
|
|
||||||
|
const appRoutes = [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
element: <HomePage />,
|
||||||
|
state: "home"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/analytics",
|
||||||
|
element: <TestAnalytics />,
|
||||||
|
state: "analytics",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Analytics",
|
||||||
|
icon: <DashboardOutlinedIcon />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/tests/insights",
|
||||||
|
element: <Devices />,
|
||||||
|
state: "insights",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Insights",
|
||||||
|
icon: <AppsOutlinedIcon />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/downloaddata",
|
||||||
|
element: <ExportData />,
|
||||||
|
state: "downloaddata",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Raw Data",
|
||||||
|
icon: <Download />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/registration",
|
||||||
|
element: <Registration />,
|
||||||
|
state: "registration",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Registration",
|
||||||
|
icon: <FaUser />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/appinstallation",
|
||||||
|
element: <AppInstallation />,
|
||||||
|
state: "appinstallation",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "App Installation",
|
||||||
|
icon: <FaAndroid />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/samples",
|
||||||
|
element: <Samples />,
|
||||||
|
state: "Samples",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Samples",
|
||||||
|
icon: <FaFlask />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/kits",
|
||||||
|
element: <KitCheck />,
|
||||||
|
state: "kits",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Kit Check",
|
||||||
|
icon: <FaBox />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/diagnostics",
|
||||||
|
element: <Diagnostics />,
|
||||||
|
state: "Diagnostics",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Device Data",
|
||||||
|
icon: <FaMicrochip />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// path: "/precision",
|
||||||
|
// element: <Precision />,
|
||||||
|
// state: "precision",
|
||||||
|
// sidebarProps: {
|
||||||
|
// displayText: "Precision",
|
||||||
|
// icon: <FaCheck />
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
path: "/autoanalysis",
|
||||||
|
element: <Accuracy />,
|
||||||
|
state: "autoanalysis",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Auto Analysis",
|
||||||
|
icon: <FaChartLine />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/plots",
|
||||||
|
element: <CurveFit />,
|
||||||
|
state: "plots",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "EDA Plots",
|
||||||
|
icon: <FaBezierCurve />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/chat",
|
||||||
|
element: <Gemini />,
|
||||||
|
state: "chat",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Chat Gemini",
|
||||||
|
icon: <FaCommentAlt />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/helpcenter",
|
||||||
|
element: <Help />,
|
||||||
|
state: "helpcenter",
|
||||||
|
sidebarProps: {
|
||||||
|
displayText: "Help Center",
|
||||||
|
icon: <FaQuestionCircle />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default appRoutes;
|
||||||
34
src/routes/index.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Route } from "react-router-dom";
|
||||||
|
import PageWrapper from "../components/PageWrapper";
|
||||||
|
import appRoutes from "./appRoutes";
|
||||||
|
|
||||||
|
const generateRoute = (routes) => {
|
||||||
|
return routes.map((route, index) => (
|
||||||
|
route.index ? (
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
path={route.path}
|
||||||
|
element={<PageWrapper state={route.state}>
|
||||||
|
{route.element}
|
||||||
|
</PageWrapper>}
|
||||||
|
key={index}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Route
|
||||||
|
path={route.path}
|
||||||
|
element={
|
||||||
|
<PageWrapper state={route.child ? undefined : route.state}>
|
||||||
|
{route.element}
|
||||||
|
</PageWrapper>
|
||||||
|
}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{route.child && (
|
||||||
|
generateRoute(route.child)
|
||||||
|
)}
|
||||||
|
</Route>
|
||||||
|
)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const routes = generateRoute(appRoutes);
|
||||||
@@ -3,3 +3,4 @@
|
|||||||
// expect(element).toHaveTextContent(/react/i)
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
|
// import '@testing-library/jest-dom/extend-expect';
|
||||||
21
src/store/appStateSlice.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
appState: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appStateSlice = createSlice({
|
||||||
|
name: "appState",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setAppState: (state, action) => {
|
||||||
|
state.appState = action.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
setAppState
|
||||||
|
} = appStateSlice.actions;
|
||||||
|
|
||||||
|
export default appStateSlice.reducer;
|
||||||
8
src/store/store.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
import appStateSlice from "./appStateSlice";
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
appState: appStateSlice
|
||||||
|
}
|
||||||
|
});
|
||||||