37 Commits

Author SHA1 Message Date
Pritimay Sarkar
7e92bf3ad1 changes samples table 2023-12-20 11:02:58 +05:30
Pritimay Sarkar
1f55978521 add reference file input and loader 2023-12-11 22:12:56 +05:30
Pritimay Sarkar
84740e846f add loading icon and fix cors 2023-12-10 23:39:27 +05:30
Pritimay Sarkar
09d18106b8 add new helps 2023-12-08 16:36:46 +05:30
Pritimay Sarkar
5dbff78d79 add new multi site support 2023-12-08 15:36:10 +05:30
Pritimay Sarkar
75ad2aba82 add remaining files 2023-12-08 15:35:46 +05:30
Pritimay Sarkar
3a355ffe57 add auto analysis functionality 2023-12-08 15:35:17 +05:30
Pritimay Sarkar
4da937673c add new samples columns 2023-12-08 15:34:46 +05:30
Pritimay Sarkar
48c9bacb4f add supported formats for samples 2023-12-08 15:34:30 +05:30
Pritimay Sarkar
4dd9a31a75 add accordion in help for automatic 2023-12-08 15:33:59 +05:30
Pritimay Sarkar
5d5325d51d add curve fit image api 2023-12-03 16:28:36 +05:30
Pritimay Sarkar
f857e57173 add device quality components 2023-12-03 11:51:12 +05:30
Pritimay Sarkar
eaf87791bd new columns 2023-11-10 16:17:25 +05:30
Pritimay Sarkar
89090ff86b install steps 2023-11-10 16:16:53 +05:30
Pritimay Sarkar
515ad41329 change topbar text 2023-11-10 16:16:33 +05:30
Pritimay Sarkar
b43aba97f2 add qa firebase config 2023-11-09 08:02:59 +05:30
Pritimay Sarkar
a836335ed3 add samples page 2023-11-09 07:49:36 +05:30
Pritimay Sarkar
260e6ec734 change apis to qa links 2023-11-07 10:52:33 +05:30
Pritimay Sarkar
eb975ce11a update app links 2023-11-07 08:26:24 +05:30
Pritimay Sarkar
a3d2bea0cc add incubation time 2023-11-07 05:55:03 +05:30
Pritimay Sarkar
420171d24a add link in qc code 2023-09-30 11:56:08 +05:30
Pritimay Sarkar
dd4ee6d964 add qr code instructions 2023-09-27 02:07:24 +05:30
Pritimay Sarkar
62810fe81b add app installtion page 2023-09-25 01:47:27 +05:30
Pritimay Sarkar
aa08336f5f button css 2023-09-25 01:26:36 +05:30
Pritimay Sarkar
66838b6360 add clearusers api 2023-09-25 01:16:33 +05:30
Pritimay Sarkar
3774972839 add clear button 2023-09-25 00:20:42 +05:30
Pritimay Sarkar
924e48c1f0 add alerts commponents 2023-09-19 15:39:43 +05:30
Pritimay Sarkar
36879b6326 registration name 2023-09-10 09:50:35 +05:30
Pritimay Sarkar
327aa07735 add preprod setup 2023-09-07 11:22:05 +05:30
Pritimay Sarkar
f269b12da1 fix error for hemocube device in select 2023-09-07 11:21:52 +05:30
Pritimay Sarkar
cb0063e286 html tag 2023-09-05 18:44:32 +05:30
Pritimay Sarkar
3e01c94e46 add pdf generation 2023-08-29 16:29:57 +05:30
Pritimay Sarkar
cde2485a60 show user id after user registration 2023-08-28 18:23:39 +05:30
Pritimay Sarkar
9893161684 add user registration 2023-08-28 17:54:20 +05:30
Pritimay Sarkar
eecc6501df add data download page 2023-08-23 21:15:32 +05:30
Pritimay Sarkar
47d348d791 merge 2023-08-23 20:57:33 +05:30
Pritimay Sarkar
16b3f521d4 add sidebar with redux
- add store
	- add routes
	- add page wrapper, mainlayout
	- add nav folder
2023-08-01 15:38:22 +05:30
75 changed files with 4713 additions and 586 deletions

View File

@@ -1,5 +1,18 @@
{
"projects": {
"default": "hpos-af3cc"
}
}
},
"targets": {
"hpos-af3cc": {
"hosting": {
"dev1": [
"hpos-dev1"
],
"qa": [
"hpos-qa"
]
}
}
},
"etags": {}
}

4
.gitignore vendored
View File

@@ -27,3 +27,7 @@ yarn-error.log*
src/components/testData.json
public/world-110m.json
public/india-states.json
*.apk
scripts/reports
scripts/data

View File

@@ -14,4 +14,22 @@
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 build
### deploy
firebase deploy --only hosting:dev1
pyinstaller --noconfirm --noconsole -i "smi.ico" --windowed --clean hemocube.py

View File

@@ -1,5 +1,6 @@
{
"hosting": {
"target": "qa",
"public": "build",
"ignore": [
"firebase.json",

View File

@@ -1,45 +0,0 @@
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println("SN:182829");
// while (Serial.available() == 0) {} //wait for data available
// String bufferCommand = Serial.readString(); //read until timeout
// bufferCommand.trim(); // remove any \r \n whitespace at the end of the String
// if (bufferCommand == "B") {
// Serial.println("#Start Buffer");
// delay(2000);
// Serial.println("#Buffer Completed");
// }
// while (Serial.available() == 0) {} //wait for data available
// String SampleCommand = Serial.readString(); //read until timeout
// SampleCommand.trim(); // remove any \r \n whitespace at the end of the String
// if (SampleCommand == "S") {
// Serial.println("#Sample Started");
// delay(2000);
// Serial.println("#Sample Completed");
// }
// while (Serial.available() == 0) {} //wait for data available
// String resultCommand = Serial.readString(); //read until timeout
// resultCommand.trim(); // remove any \r \n whitespace at the end of the String
// if (resultCommand == "R") {
// delay(3000);
// Serial.println("RESULT SN 18291 1937.23 2828.11 28211.20 121829.12 REND");
// delay(15000);
// }
while (Serial.available() == 0) {} //wait for data available
String resultCommand = Serial.readString(); //read until timeout
resultCommand.trim(); // remove any \r \n whitespace at the end of the String
if (resultCommand == "B") {
delay(3000);
Serial.println("ERROR 500 REND");
delay(15000);
}
}

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

3007
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,11 @@
"version": "0.1.0",
"private": true,
"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/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
@@ -14,10 +19,12 @@
"ml-savitzky-golay-generalized": "^4.0.1",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-datepicker": "^4.16.0",
"react-dom": "^18.2.0",
"react-dropdown": "^1.11.0",
"react-flip-numbers": "^3.0.8",
"react-icons": "^4.10.1",
"react-redux": "^8.1.2",
"react-router-dom": "^6.14.2",
"react-scripts": "5.0.1",
"react-select": "^5.7.4",
@@ -49,5 +56,9 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"csv-parser": "^3.0.0",
"html-pdf-node": "^1.0.8"
}
}

Binary file not shown.

View File

@@ -1,64 +0,0 @@
import os
import firebase_admin
from IPython.core.display import Image
from IPython.core.display_functions import display
from firebase_admin import credentials
from firebase_admin import firestore
import pandas as pd
import datetime
print("pritimay")
# Initialize Firebase Admin SDK
cred = credentials.Certificate(r'C:\Users\smila\PycharmProjects\pythonProject\hpos-prod-firebase-adminsdk-bionp-5486f7becd.json') # Replace with your own service account key path
firebase_admin.initialize_app(cred)
# Get a reference to the Firestore database
db = firestore.client()
# Specify the collections
patient_collection = db.collection("patientData")
test_collection = db.collection("testData")
start_date = input("Please enter the start date (yyyy-mm-dd): ")
end_date = input("Please enter the end date (yyyy-mm-dd): ")
query = patient_collection.where("createdAt", ">=", start_date).where("createdAt", "<", end_date)
patient_docs = query.stream()
# Prepare data to store in CSV
data = []
for patient_doc in patient_docs:
patient_data = patient_doc.to_dict()
patient_id = patient_data["_id"]
# Query the document from testData collection based on the common _id
test_docs = test_collection.where("_id", "==", patient_id).stream()
for test_doc in test_docs:
test_data = test_doc.to_dict()
# Combine the data from both collections into a single dictionary
combined_data = {**patient_data, **test_data}
# Skip if the csvPath is not a valid URL
data.append(combined_data)
# Convert the data to a DataFrame
df = pd.DataFrame(data)
print(df.size)
# Save the DataFrame to a CSV file
output_filename = "data.csv"
df.to_csv(output_filename, index=False)
downloads_dir = os.path.join(os.path.expanduser("~"), "Downloads")
output_path = os.path.join(downloads_dir, output_filename)
df.to_csv(output_path, index=False)
print(f"Data saved to '{output_path}'")
# Close the Firebase Admin SDK
firebase_admin.delete_app(firebase_admin.get_app())

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

281
scripts/pdf.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,235 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<style>
@media print {
body {
-webkit-print-color-adjust: exact;
}
}
.person-details-row {
display: flex;
flex-direction: row;
}
.person-details-col {
display: flex;
flex-direction: column;
height: 90px;
width: 50%;
margin: 1px;
border: 0;
}
.test-details-row {
border: 0;
}
.test-details-col {
height: 100px;
}
.test-cell-div {
/* border: 1px solid black; */
border-collapse: collapse;
height: 20px;
padding: 10px;
}
.table-header {
border: 1px solid black;
margin: 0 0 -10px 10px;
background-color: rgb(191, 191, 191);
text-align: center;
justify-content: center;
align-items: center;
display: flex;
font-weight: 600;
height: 50px;
width: 98%;
}
@media print {
.table-header {
background-color: rgb(191, 191, 191) !important;
print-color-adjust: exact;
}
}
@media print {
.vendorListHeading th {
color: white !important;
}
}
.test-method {
font-weight: 200 !important;
color: rgb(191, 191, 191);
}
.result-value {
justify-content: center;
align-items: center;
display: flex;
}
.end-of-report {
display: flex;
align-items: center;
justify-content: center;
}
.logo {
display: flex;
justify-content: center;
align-items: center;
}
table,
th,
td {
border: 1px solid black;
border-collapse: collapse;
}
ul {
list-style-type: none;
/* margin: 0; */
/* padding: 0; */
}
</style>
</head>
<body>
<div>
<div class="logo">
<img src="./logo_nobg.png" width="128" height="128" alt="sickle cell logo" />
</div>
<table style="border: 1px solid black; margin: 10px; width: 98%;">
<tr class="person-details-row">
<td class="person-details-col" style="border-right: 2; width: 70%;">
<div><strong>Name:</strong></div>
<div><strong>Age / Gender:</strong></div>
<div><strong>Sample type:</strong></div>
<div><strong>Family History of Sickle Cell Anemia:</strong></div>
</td>
<td class="person-details-col" style="width: 30%;">
<div><strong>Marital Status:</strong></div>
<div><strong>Test Date:</strong></div>
<div><strong>ABHA ID:</strong></div>
<div><strong>Sample ID:</strong></div>
</td>
</tr>
</table>
</div>
<div style="height: 2px;width: 98%; background-color: rgb(56, 105, 166); margin: 30px 10px 40px 10px;"></div>
<div>
<div class="table-header">POINT OF CARE SICKLE CELL ANEMIA TEST</div>
<table class="test-details-table" style="border: 1px solid black; margin: 10px; width: 98%;">
<tr style="height: 50px;">
<th>
Test Description
</th>
<th>
RESULT
</th>
<th>
REFERENCE RANGES
</th>
</tr>
<tr class="test-details-row">
<td class="test-details-col" style="width: 20%; margin: 10px;">
<div style="margin: 10px;">Sickle Cell
Anemia
<div class="test-method">(Method: HPOS)</div>
</div>
</td>
<td style="width: 30%;">
<div class="result-value">Ra = 0.16</div>
<!-- <div class="test-cell-div">Normal</div>
<div class="test-cell-div">Sickle Cell Trait</div>
<div class="test-cell-div">Sickle Cell Disease</div>
<div class="test-cell-div">Negative Borderline</div>
<div class="test-cell-div">Positive Borderline</div> -->
</td>
<td style="width: 50%;">
<div class="test-cell-div">
< 0.16: Normal (HbA)</div>
<div class="test-cell-div">0.165 0.235: Sickle-cell Trait (HbAS)</div>
<div class="test-cell-div">> 0.24: Sickle-cell Disease (HbSS)</div>
<div class="test-cell-div">0.16-0.165: Inconclusive (Negative Borderline)</div>
<div class="test-cell-div">0.235 0.24: Inconclusive (Positive Borderline)</div>
</td>
<!-- <td style="width: 25%;">
<div class="test-cell-div">Normal</div>
<div class="test-cell-div">Sickle Cell Trait</div>
<div class="test-cell-div">Sickle Cell Disease</div>
<div class="test-cell-div">Recommended for HPLC or Electrophoresis Tests</div>
<div class="test-cell-div">Recommended for HPLC or Electrophore</div>
</td> -->
</tr>
</table>
</div>
<div>
<!-- <div style="font-weight: 600; margin: 10px;">INTERPRETATION:</div> -->
<div style="margin: 10px;">
<strong>Test Principle:</strong> This point of care quantitative diagnostic test for sickle-cell anemia
works on the principle of absorption
spectroscopy. The test helps in differentiating heterozygous/homozygous hemoglobin from normal hemoglobin.
</div>
<div style="margin: 10px;">
<strong>Method:</strong> High Performance Optical Spectroscopy (HPOS) for detection of Sickle cell trait and
sickle cell disease in whole blood capillary blood samples.
</div>
<div style="margin: 10px;">
<strong> Note:</strong>
<!-- <ul>
<li>a. Blood transfusion may have an impact on the test results.</li>
<li>
b. Patients already on sickle-cell medications may impact test results.
</li>
</ul> -->
Borderline cases are reported as inconclusive. It may occur due to several factors such as medication,
transfusion, field conditions and assay process. Further clinical tests are recommended in these cases for
diagnosis.
</div>
<div
style="margin: 80px 10px 20px 50px; width: 88%; display: flex; flex-direction: row; justify-content: space-between; align-items: center;">
<div style="margin: 5px;">DATE:</div>
<div style="margin: 5px;">Hematologist</div>
</div>
<!-- <div style="margin: 30px;">
This report is for the perusal of doctor only. Not for medico legal cases. Clinical correlation is
essential.
Please contact us in case of unexpected result.
</div> -->
<div style="height: 3px;width: 98%; background-color: black;"></div>
<div class="end-of-report">
*** END OF REPORT ***</div>
<div style="display: flex; justify-content: center; align-items: center; flex-direction: column;">
<div style="font-style: italic;">
This is an electronically generated report. Generated at HH:MM hrs on DD-MMM-YYYY.
</div>
<div>
Note: Assay results should be correlated clinically with other clinical findings
</div>
</div>
</div>
</body>
</html>

View File

@@ -50,7 +50,7 @@ body {
display: inline-flex;
flex-direction: row;
font-size: xx-large;
background-color: rgb(207, 235, 250);
background-color: rgb(215, 192, 252);
}
.last-update {
@@ -146,7 +146,7 @@ body {
text-align: center;
align-self: center;
font-size: xx-large;
background-color: rgb(207, 235, 250);
background-color: rgb(215, 192, 252);
}
.nscm-logo {
@@ -206,7 +206,7 @@ body {
text-align: center;
align-self: center;
font-size: xx-large;
background-color: rgb(207, 235, 250);
background-color: rgb(215, 192, 252);
}
}
@@ -253,7 +253,7 @@ body {
text-align: center;
align-self: center;
font-size: xx-large;
background-color: rgb(207, 235, 250);
background-color: rgb(215, 192, 252);
}
.title-text {
@@ -289,7 +289,7 @@ body {
}
.cards-container {
height: 90vh;
height: 80vh;
justify-content: center;
align-items: center;
display: flex;
@@ -307,7 +307,7 @@ body {
text-align: center;
align-self: center;
font-size: xx-large;
background-color: rgb(207, 235, 250);
background-color: rgb(215, 192, 252);
}
.title-text {
@@ -356,7 +356,7 @@ body {
}
.cards-container {
height: 90vh;
height: 80vh;
justify-content: center;
align-items: center;
display: flex;
@@ -372,7 +372,7 @@ body {
.dashboard-title {
text-align: center;
align-self: center;
background-color: rgb(207, 235, 250);
background-color: rgb(215, 192, 252);
}
.title-text {

View File

@@ -2,11 +2,27 @@ import './App.css';
import React, { useEffect, useState } from 'react';
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 Card from './components/Card';
import db from './firebase';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<MainLayout />}>
{routes}
</Route>
</Routes>
</BrowserRouter>
);
}
function App1() {
const [totalScreened, setTotalScreened] = useState(0);
const [males, setMales] = useState(0);
const [females, setFemales] = useState(0);

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View 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
View 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-hpos-qa.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;

View File

39
src/components/Alerts.js Normal file
View 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;

View File

@@ -0,0 +1,4 @@
.app-qrcode {
height: 250px;
width: 250px;
}

View 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;

View File

@@ -1,9 +0,0 @@
.page-title {
/* width: 100vw; */
padding: 10px;
text-align: center;
background: rgb(207, 235, 250);
color: black;
font-size: 30px;
font-family: sans-serif;
}

View File

@@ -1,78 +0,0 @@
import React, { useEffect, useState } from 'react';
import { query, collection, onSnapshot } from 'firebase/firestore';
import Dropdown from 'react-dropdown';
import 'react-dropdown/style.css';
import moment from 'moment';
import db from '../firebase';
import './Calibrations.css';
const Calibrations = () => {
const [device, setDevice] = useState("Select a device");
const [devices, setDevices] = useState([]);
const [curDeviceData, setCurDeviceData] = useState(null);
const changeDevice = ({ value }) => {
setDevice(value);
}
const toggleShowDevice = () => {
console.log(devices.filter(x => x.deviceId === device))
setCurDeviceData(devices.find(x => x.deviceId === device));
// if (!device) {
// setCurDeviceData(devices.filter(x => x.deviceId === device));
// } else {
// setDevice(null);
// }
}
useEffect(() => {
if (!devices?.length) {
const q = query(collection(db, "devices"));
const unsub = onSnapshot(q, (querySnapshot) => {
const testDevices = [];
querySnapshot.forEach((document) => {
const deviceData = document.data();
testDevices.push(deviceData);
});
setDevices(testDevices);
});
return () => unsub;
}
}, [device, curDeviceData]);
return (
<div className="calibrations-page">
<div className='page-title'>Calibrations</div>
<div className='dropdown-group'>
<Dropdown className='device-dropdown' options={devices?.map(x => x.deviceId)} onChange={changeDevice} value={device} placeholder="Select an device" />
<button className='clear-button' onClick={toggleShowDevice}><img className="graph-icon" src={require('../assets/graph_icon.png')} alt='icon'></img>{device ? 'Clear' : 'Show'}</button>
</div>
<div className='graph-person-conatiner'>
{curDeviceData && <div className='test-details'>
<table>
<tr>
<th>key</th>
<th>value</th>
</tr>
<tr>
<td>device</td> <td>{curDeviceData?.deviceId} </td>
</tr>
<tr>
<td>calibratedAt</td> <td>{curDeviceData?.calibratedAt} </td>
</tr>
<tr>
<td>coefficients</td> <td>{curDeviceData?.coefficients?.toString()} </td>
</tr>
</table>
</div>}
</div>
</div>
)
}
export default Calibrations;

View File

View File

@@ -0,0 +1,56 @@
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 fetchSvg = () => {
axios({
method: 'get',
url: "https://asia-south1-hpos-qa.cloudfunctions.net/curvefit",
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) => {
// setMessage(`${id} is being created`);
// setSvgData(data?.plot?.replace('\"', '').replace('\n', '').replace('"', "'"));
})
.catch((err) => {
console.log(err);
// setMessage(`error: ${err?.message}`);
});
}
useEffect(() => {
if (!svgData) {
// fetchSvg();
}
}, []);
return (
<div>
<h1>Curve Fit</h1>
{/* <img alt='' src={ svgData }/>; */}
<img src="https://asia-south1-hpos-qa.cloudfunctions.net/curvefit" width="450" height="340" />
{/* <ImageDownloader /> */}
</div>
);
}
export default CurveFit;

View File

@@ -2,14 +2,12 @@
display: none;
}
.insights-title {
/* width: 100vw; */
.title {
padding: 10px;
text-align: center;
background: rgb(207, 235, 250);
background: rgb(215, 192, 252);
color: black;
font-size: 30px;
font-family: sans-serif;
}
.device-dropdown {
@@ -81,7 +79,7 @@
background-color: rgb(207, 235, 250);
width: 10vw;
height: 5vh;
/* margin: 5px; */
margin: 5px;
border-radius: 10px;
font-size: medium;
font-weight: 600;

View File

@@ -1,9 +1,8 @@
import React, { useEffect, useState, useRef } from 'react';
import React, { useEffect, useState } from 'react';
import { query, collection, onSnapshot } from 'firebase/firestore';
import Dropdown from 'react-dropdown';
import 'react-dropdown/style.css';
import moment from 'moment';
import Select from 'react-select';
import DeviceGraph from "./DeviceGraph";
import db from '../firebase';
@@ -19,17 +18,13 @@ const Devices = () => {
const [tests, setTests] = useState([]);
const [allTests, setAllTests] = useState(null);
const [showGraph, setShowGraph] = useState(false);
const selectDateDropdownRef = useRef();
const selectTestDropdownRef = useRef();
const changeDevice = ({ value }) => {
setDevice(value);
setTestDate(null);
setCurTest(null);
selectDateDropdownRef.current?.setValue("");
selectTestDropdownRef.current?.setValue("");
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")))]
.sort()
.reverse());
@@ -37,12 +32,11 @@ const Devices = () => {
const changeTestDate = ({ value }) => {
setTestDate(value);
selectTestDropdownRef.current?.setValue("");
setCurTest(null);
setCurTestData(null);
setShowGraph(false);
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)
.sort());
}
@@ -76,8 +70,13 @@ const Devices = () => {
querySnapshot.forEach((document) => {
const hposTestData = document.data();
hposTests.push(hposTestData);
const { deviceSerialNumber } = hposTestData;
testDevices.add(deviceSerialNumber);
const { deviceType, deviceId, deviceSerialNumber } = hposTestData;
if (deviceType === 'HEMOCUBE' && deviceId) {
testDevices.add(deviceId);
}
if (deviceType !== 'HEMOCUBE' && deviceSerialNumber) {
testDevices.add(deviceSerialNumber);
}
});
const groups = [...testDevices];
setDevices(groups);
@@ -90,45 +89,22 @@ const Devices = () => {
return (
<div className="devices-page">
<div className='insights-title'>Insights</div>
<div className='title'>Insights</div>
<div className='dropdown-group'>
{/* <Dropdown className='device-dropdown' options={devices} onChange={changeDevice} value={device} placeholder="Select an device" />
<Dropdown className='device-dropdown' options={devices} onChange={changeDevice} value={device} placeholder="Select an device" />
<Dropdown className='day-dropdown' options={days} onChange={changeTestDate} value={testDate} placeholder="Select an test date" />
<Dropdown className='test-dropdown' options={tests} onChange={changeTest} value={curTest} placeholder="Select an test" /> */}
<Dropdown className='test-dropdown' options={tests} onChange={changeTest} value={curTest} placeholder="Select an test" />
<Select
className='device-dropdown'
defaultValue={{ label: "Select a device", value: "Select a device" }}
options={devices.map(x => { return { value: x, label: x }; })}
menuPlacement='auto'
maxMenuHeight={250}
menuShouldScrollIntoView={true}
onChange={changeDevice} />
<Select
ref={selectDateDropdownRef}
className='day-dropdown'
defaultValue={{ label: "Select a test date", value: "Select a test date" }}
options={days.map(x => { return { value: x, label: x }; })}
menuPlacement='auto'
maxMenuHeight={250}
menuShouldScrollIntoView={true}
onChange={changeTestDate} />
<Select
ref={selectTestDropdownRef}
className='test-dropdown'
defaultValue={{ label: "Select a test", value: "Select a test" }}
options={tests.map(x => { return { value: x, label: x }; })}
menuPlacement='auto'
maxMenuHeight={220}
menuShouldScrollIntoView={true}
onChange={changeTest} />
{/* <Select className='device-dropdown' options={devices.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={250} menuShouldScrollIntoView={true} onChange={changeDevice} />
<Select className='day-dropdown' options={days.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={250} menuShouldScrollIntoView={true} onChange={changeTestDate} />
<Select className='test-dropdown' options={tests.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={220} menuShouldScrollIntoView={true} onChange={changeTestDate} /> */}
<button className='clear-button' onClick={toggleShowGraph}><img className="graph-icon" src={require('../assets/graph_icon.png')} alt='icon'></img>{curTestData ? 'Clear' : 'Show'}</button>
</div>
<div className='graph-person-conatiner'>
{!curTestData && <div className='refresh-graph-message'>Select test device, date and person to get the detailed graph. Then, click show button to render graph.</div>}
{curTestData?.csvPath && curTestData?.testType !== 'HEMOCUBE' && <DeviceGraph url={curTestData?.csvPath} show={showGraph} />}
{curTestData?.csvPath && <DeviceGraph url={curTestData?.csvPath} show={showGraph} />}
{curTestData && <div className='test-details'>
<table>
@@ -151,26 +127,6 @@ const Devices = () => {
<tr>
<td>result</td> <td>{curTestData?.result}</td>
</tr>
{curTestData?.testType === 'HEMOCUBE' && <tr>
<td>calculatedRatio</td>
<td>{curTestData?.calculatedRatio}</td>
</tr>}
{curTestData?.testType === 'HEMOCUBE' && <tr>
<td>deviceRatio</td>
<td>{curTestData?.deviceRatio}</td>
</tr>}
{curTestData?.testType === 'HEMOCUBE' && <tr>
<td>green led Average</td>
<td>{curTestData?.led1Average}</td>
</tr>}
{curTestData?.testType === 'HEMOCUBE' && <tr>
<td>blue led Average</td>
<td>{curTestData?.led2Average}</td>
</tr>}
{curTestData?.testType === 'HEMOCUBE' && <tr>
<td>blue led Average</td>
<td>{curTestData?.led2Average}</td>
</tr>}
</table>
{/* <img src={curTestData?.userImageURL} width={100} height={150} /> */}
</div>}

View 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;
}

View 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-hpos-qa.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;

19
src/components/Help.css Normal file
View 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
View 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;

View File

@@ -0,0 +1,9 @@
import React from 'react';
const HomePage = (props) => {
return (
<div>HomePage</div>
);
};
export default HomePage;

View 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;

View File

@@ -1,6 +1,6 @@
.locations-card {
width: 40vw;
height: 40vh;
height: 45vh;
background-color: white;
margin: 10px;
display: flex;
@@ -15,7 +15,7 @@
.locations-graph-card {
width: 40vw;
height: 40vh;
height: 45vh;
background-color: white;
margin: 10px;
border-style: solid;
@@ -27,7 +27,7 @@
.locations-card-title {
text-align: center;
font-weight: 600;
/* padding-top: 10px; */
padding-top: 10px;
display: flex;
align-items: center;
justify-content: center;

View 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;

View 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;

View File

View 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;

View 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;
}

View 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-hpos-qa.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-hpos-qa.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;

View File

@@ -0,0 +1,38 @@
#samples-table {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#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;
}

77
src/components/Samples.js Normal file
View File

@@ -0,0 +1,77 @@
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.data());
const alert = document.data();
samples.push(alert);
});
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>Device</th>
<th>Classification</th>
<th>abs1</th>
<th>abs2</th>
<th>abs3</th>
<th>PDR</th>
<th>Time</th>
</tr>
{samples.map((sample, index) => (
<tr key={index} className='samples-table-tr'>
<td>{sample.name}</td>
<td>{sample.deviceId}</td>
<td>{sample.prdClassification || 'NA'}</td>
<td>{sample.abs1}</td>
<td>{sample.abs2 || 'NA'}</td>
<td>{sample.abs3 || 'NA'}</td>
<td>{sample.predictedDenovixRatio}</td>
<td>{sample.testTime}</td>
</tr>
))}
</table>
</div>
</div>
)
}
export default Samples;

View File

@@ -6,7 +6,7 @@
.resultratio-graph-card {
width: 40vw;
height: 40vh;
height: 45vh;
background-color: white;
margin: 10px;
border-style: solid;

View File

@@ -24,7 +24,7 @@ class ScatterPlot extends Component {
if (!data) return;
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 40, left: 60 },
var margin = { top: 10, right: 30, bottom: 30, left: 60 },
width = 460 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;

View File

@@ -1,38 +1,17 @@
.analytics-page::-webkit-scrollbar {
display: none;
}
.analytics-title::-webkit-scrollbar {
display: none;
}
.analytics-page {
width: 100vw;
}
.analytics-container {
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: sans-serif;
}
.analytics-title {
padding: 10px;
text-align: center;
background: rgb(207, 235, 250);
color: black;
font-size: 30px;
font-family: sans-serif;
height: 50vh;
}
@media screen and (min-width: 900px) {
.analytics-container {
.container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 45vh;
height: 50vh;
}
}

View File

@@ -82,8 +82,8 @@ const TestAnalytics = () => {
}, [curTestData, curTest, device, testDate]);
return (
<div className="analytics-page">
<div className='analytics-title'>Analytics</div>
<div className="devices-page">
<div className='title'>Analytics</div>
{/* <div className='dropdown-group'>
<Dropdown className='device-dropdown' options={devices} onChange={changeDevice} value={device} placeholder="Select an device" />
<Dropdown className='day-dropdown' options={days} onChange={changeTestDate} value={testDate} placeholder="Select an test date" />
@@ -92,11 +92,11 @@ const TestAnalytics = () => {
<button className='clear-button' onClick={toggleShowGraph}><img className="graph-icon" src={require('./graph_icon.png')}></img>{curTestData ? 'Clear' : 'Show'}</button>
</div> */}
<div className='analytics-container'>
<div className='container'>
{dailyCategoryCounts && <TestAnalyticsPie data={dailyCategoryCounts} />}
{dailyResults && <TestAnalyticsGraph data={dailyResults} />}
</div>
<div className='analytics-container'>
<div className='container'>
{allTests && <ScatterPlot data={allTests} />}
{allTests && <Locations />}
</div>

View File

@@ -6,7 +6,7 @@
.tests-count-card {
width: 40vw;
height: 40vh;
height: 45vh;
background-color: white;
margin: 10px;
/* display: flex;
@@ -58,18 +58,4 @@
font-size: x-small;
font-weight: 400;
padding: 0.5em;
}
div#tests-count-graph {
height: 180px;
width: 430px;
/* border: 2px solid #000; */
overflow-y: auto;
}
svg#bargraph {
height: 180px; /* 500px; */
width: 700px; /* 1100px; */
/* border: 1px dotted #ccc; */
/* background-color: #ccc; */
}

View File

@@ -26,11 +26,10 @@ class TestAnalyticsGraph extends Component {
data.reverse();
var margin = { top: 30, right: 20, bottom: 50, left: 15 },
width = 700 - margin.left - margin.right,
width = 1500 - margin.left - margin.right,
height = 180 - margin.top - margin.bottom;
var svg = d3.select("#tests-count-graph").append('svg')
.attr("id", "bargraph")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g").attr("transform",
@@ -41,7 +40,7 @@ class TestAnalyticsGraph extends Component {
yScale = d3.scaleLinear().range([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + 5 + "," + 5 + ")");
.attr("transform", "translate(" + 10 + "," + 10 + ")");
xScale.domain(data.map(function (d) { return moment(d.date).format("MM-DD"); }));

View File

@@ -6,7 +6,7 @@
.category-graph-card {
width: 40vw;
height: 40vh;
height: 45vh;
background-color: white;
margin: 10px;
border-style: solid;

View File

@@ -24,7 +24,7 @@ class TestAnalyticsPie extends Component {
drawChart(data) {
if (!data) return;
var margin = { top: 15, right: 10, bottom: 45, left: 10 },
var margin = { top: 30, right: 10, bottom: 15, left: 25 },
width = 480 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom,
radius = Math.min(width, height) / 2;

View File

@@ -44,7 +44,7 @@ const WorldMap = () => {
}
return (
<svg width={ '40vw' } height={ '35vh' } viewBox="0 0 800 450">
<svg width={ '40vw' } height={ '50vh' } viewBox="0 0 800 450">
<g className="countries">
{
geographies.map((d,i) => (

View 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;

View 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;

View 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;

View 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 - Quality / QA
</Typography>
</Toolbar>
</AppBar>
);
};
export default Topbar;

View 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;

View File

@@ -0,0 +1,7 @@
const sizeConfigs = {
sidebar: {
width: "300px"
}
};
export default sizeConfigs;

View File

@@ -14,17 +14,39 @@ import { getAnalytics } from "firebase/analytics";
// measurementId: "G-6JHQ47S9DD"
// };
/* qa: hpos-qa */
const firebaseConfig = {
apiKey: "AIzaSyCF7BNZ5vOMQIpKRi_vxquzWP5pIjcb59I",
authDomain: "hpos-qa.firebaseapp.com",
projectId: "hpos-qa",
storageBucket: "hpos-qa.appspot.com",
messagingSenderId: "1004619739289",
appId: "1:1004619739289:web:c3ee0c0127f0d073e5c808",
measurementId: "G-4MSTJHE64W"
};
// /* preprod */
// const firebaseConfig = {
// apiKey: "AIzaSyB2-suWmKh2PgQbRmx7rzPjYhgoovQIsQ8",
// authDomain: "hpos-preprod.firebaseapp.com",
// projectId: "hpos-preprod",
// storageBucket: "hpos-preprod.appspot.com",
// messagingSenderId: "121176529204",
// appId: "1:121176529204:web:6c0db8e642992192bbed61",
// measurementId: "G-GYM6LRQ8KR"
// };
/* 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"
};
// /* 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);

View File

@@ -4,6 +4,9 @@ import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import { Provider } from 'react-redux';
import { store } from './store/store';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
@@ -11,7 +14,7 @@ import Devices from './components/Devices';
import TestAnalytics from './components/TestAnalytics';
import WorldMap from './components/WorldMap';
import RegistraionChart from './RegistraionChart';
import Calibrations from './components/Calibrations';
import TestAnalyticsGraph from './components/TestAnalyticsGraph';
const router = createBrowserRouter([
{
@@ -31,19 +34,23 @@ const router = createBrowserRouter([
element: <TestAnalytics />,
},
{
path: "/map",
element: <RegistraionChart />,
path: "/hposdata",
element: <TestAnalyticsGraph />,
},
{
path: "/calibrations",
element: <Calibrations />,
path: "/map",
element: <RegistraionChart />,
}
]);
const root = ReactDOM.createRoot(document.getElementById('root'));
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

116
src/routes/appRoutes.js Normal file
View File

@@ -0,0 +1,116 @@
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, FaChalkboardTeacher, FaChartArea, FaChartLine, FaCheck, FaExclamation, FaFlask, FaGolfBall, 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';
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: "/precision",
// element: <Precision />,
// state: "precision",
// sidebarProps: {
// displayText: "Precision",
// icon: <FaCheck />
// }
// },
{
path: "/autoanalysis",
element: <Accuracy />,
state: "autoanalysis",
sidebarProps: {
displayText: "Auto Analysis (NEW)",
icon: <FaChartLine />
}
},
// {
// path: "/curvefit",
// element: <CurveFit />,
// state: "curvefit",
// sidebarProps: {
// displayText: "Linear Fit",
// icon: <FaBezierCurve />
// }
// },
{
path: "/helpcenter",
element: <Help />,
state: "helpcenter",
sidebarProps: {
displayText: "Help Center",
icon: <FaQuestionCircle />
}
},
]
export default appRoutes;

34
src/routes/index.js Normal file
View 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);

View 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
View File

@@ -0,0 +1,8 @@
import { configureStore } from "@reduxjs/toolkit";
import appStateSlice from "./appStateSlice";
export const store = configureStore({
reducer: {
appState: appStateSlice
}
});