Compare commits
24 Commits
dev
...
Mariya_Var
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd252b6ad | ||
|
|
31a4b3f278 | ||
|
|
f8a8c6a789 | ||
|
|
0715733163 | ||
|
|
aefcecf90f | ||
|
|
ebd9d23f12 | ||
|
|
e681a4a9bf | ||
|
|
f17408f5ce | ||
|
|
60a8044041 | ||
|
|
2d040ff1c8 | ||
|
|
e9da7a7983 | ||
|
|
4579ad914d | ||
|
|
b4ab8cbff0 | ||
|
|
d35510d39a | ||
|
|
5af8197664 | ||
|
|
29b7db446e | ||
|
|
ca71641341 | ||
|
|
45aecc658e | ||
|
|
5524f135ff | ||
|
|
65f5fdc428 | ||
|
|
4bf75a2473 | ||
|
|
87e4ca7c9a | ||
|
|
e34a618198 | ||
|
|
f9c054c62b |
45
firmware/hemocube_mock/hemocube_mock.ino
Normal file
45
firmware/hemocube_mock/hemocube_mock.ino
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
scripts/224321944144PADGMC.pdf
Normal file
BIN
scripts/224321944144PADGMC.pdf
Normal file
Binary file not shown.
64
scripts/UserCollection.py
Normal file
64
scripts/UserCollection.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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())
|
||||||
BIN
scripts/logo.png
Normal file
BIN
scripts/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
scripts/logo_nobg.png
Normal file
BIN
scripts/logo_nobg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
235
scripts/report_template.html
Normal file
235
scripts/report_template.html
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<!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>
|
||||||
16
src/App.css
16
src/App.css
@@ -50,7 +50,7 @@ body {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
background-color: rgb(215, 192, 252);
|
background-color: rgb(207, 235, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
.last-update {
|
.last-update {
|
||||||
@@ -146,7 +146,7 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
background-color: rgb(215, 192, 252);
|
background-color: rgb(207, 235, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nscm-logo {
|
.nscm-logo {
|
||||||
@@ -206,7 +206,7 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
background-color: rgb(215, 192, 252);
|
background-color: rgb(207, 235, 250);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
background-color: rgb(215, 192, 252);
|
background-color: rgb(207, 235, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-text {
|
.title-text {
|
||||||
@@ -289,7 +289,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cards-container {
|
.cards-container {
|
||||||
height: 80vh;
|
height: 90vh;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -307,7 +307,7 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
background-color: rgb(215, 192, 252);
|
background-color: rgb(207, 235, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-text {
|
.title-text {
|
||||||
@@ -356,7 +356,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cards-container {
|
.cards-container {
|
||||||
height: 80vh;
|
height: 90vh;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -372,7 +372,7 @@ body {
|
|||||||
.dashboard-title {
|
.dashboard-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
background-color: rgb(215, 192, 252);
|
background-color: rgb(207, 235, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-text {
|
.title-text {
|
||||||
|
|||||||
9
src/components/Calibrations.css
Normal file
9
src/components/Calibrations.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.page-title {
|
||||||
|
/* width: 100vw; */
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
background: rgb(207, 235, 250);
|
||||||
|
color: black;
|
||||||
|
font-size: 30px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
78
src/components/Calibrations.js
Normal file
78
src/components/Calibrations.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
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;
|
||||||
@@ -2,12 +2,14 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.insights-title {
|
||||||
|
/* width: 100vw; */
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: rgb(215, 192, 252);
|
background: rgb(207, 235, 250);
|
||||||
color: black;
|
color: black;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-dropdown {
|
.device-dropdown {
|
||||||
@@ -79,7 +81,7 @@
|
|||||||
background-color: rgb(207, 235, 250);
|
background-color: rgb(207, 235, 250);
|
||||||
width: 10vw;
|
width: 10vw;
|
||||||
height: 5vh;
|
height: 5vh;
|
||||||
margin: 5px;
|
/* margin: 5px; */
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
font-size: medium;
|
font-size: medium;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { query, collection, onSnapshot } from 'firebase/firestore';
|
import { query, collection, onSnapshot } from 'firebase/firestore';
|
||||||
import Dropdown from 'react-dropdown';
|
import Dropdown from 'react-dropdown';
|
||||||
import 'react-dropdown/style.css';
|
import 'react-dropdown/style.css';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import Select from 'react-select';
|
||||||
|
|
||||||
import DeviceGraph from "./DeviceGraph";
|
import DeviceGraph from "./DeviceGraph";
|
||||||
import db from '../firebase';
|
import db from '../firebase';
|
||||||
@@ -18,11 +19,15 @@ const Devices = () => {
|
|||||||
const [tests, setTests] = useState([]);
|
const [tests, setTests] = useState([]);
|
||||||
const [allTests, setAllTests] = useState(null);
|
const [allTests, setAllTests] = useState(null);
|
||||||
const [showGraph, setShowGraph] = useState(false);
|
const [showGraph, setShowGraph] = useState(false);
|
||||||
|
const selectDateDropdownRef = useRef();
|
||||||
|
const selectTestDropdownRef = useRef();
|
||||||
|
|
||||||
const changeDevice = ({ value }) => {
|
const changeDevice = ({ value }) => {
|
||||||
setDevice(value);
|
setDevice(value);
|
||||||
setTestDate(null);
|
setTestDate(null);
|
||||||
setCurTest(null);
|
setCurTest(null);
|
||||||
|
selectDateDropdownRef.current?.setValue("");
|
||||||
|
selectTestDropdownRef.current?.setValue("");
|
||||||
setDays([...new Set(allTests
|
setDays([...new Set(allTests
|
||||||
.filter((x) => x.deviceSerialNumber === value)
|
.filter((x) => x.deviceSerialNumber === value)
|
||||||
.map(x => moment(x.testTime).format("YYYY-MM-DD")))]
|
.map(x => moment(x.testTime).format("YYYY-MM-DD")))]
|
||||||
@@ -32,6 +37,7 @@ const Devices = () => {
|
|||||||
|
|
||||||
const changeTestDate = ({ value }) => {
|
const changeTestDate = ({ value }) => {
|
||||||
setTestDate(value);
|
setTestDate(value);
|
||||||
|
selectTestDropdownRef.current?.setValue("");
|
||||||
setCurTest(null);
|
setCurTest(null);
|
||||||
setCurTestData(null);
|
setCurTestData(null);
|
||||||
setShowGraph(false);
|
setShowGraph(false);
|
||||||
@@ -84,22 +90,45 @@ const Devices = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="devices-page">
|
<div className="devices-page">
|
||||||
<div className='title'>Insights</div>
|
<div className='insights-title'>Insights</div>
|
||||||
<div className='dropdown-group'>
|
<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='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' options={devices.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={250} menuShouldScrollIntoView={true} onChange={changeDevice} />
|
<Select
|
||||||
<Select className='day-dropdown' options={days.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={250} menuShouldScrollIntoView={true} onChange={changeTestDate} />
|
className='device-dropdown'
|
||||||
<Select className='test-dropdown' options={tests.map(x => { return { value: x, label: x }; })} menuPlacement='auto' maxMenuHeight={220} menuShouldScrollIntoView={true} onChange={changeTestDate} /> */}
|
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} />
|
||||||
|
|
||||||
<button className='clear-button' onClick={toggleShowGraph}><img className="graph-icon" src={require('../assets/graph_icon.png')} alt='icon'></img>{curTestData ? 'Clear' : 'Show'}</button>
|
<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>
|
||||||
|
|
||||||
<div className='graph-person-conatiner'>
|
<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 && <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 && <DeviceGraph url={curTestData?.csvPath} show={showGraph} />}
|
{curTestData?.csvPath && curTestData?.testType !== 'HEMOCUBE' && <DeviceGraph url={curTestData?.csvPath} show={showGraph} />}
|
||||||
|
|
||||||
{curTestData && <div className='test-details'>
|
{curTestData && <div className='test-details'>
|
||||||
<table>
|
<table>
|
||||||
@@ -122,6 +151,26 @@ const Devices = () => {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>result</td> <td>{curTestData?.result}</td>
|
<td>result</td> <td>{curTestData?.result}</td>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
{/* <img src={curTestData?.userImageURL} width={100} height={150} /> */}
|
{/* <img src={curTestData?.userImageURL} width={100} height={150} /> */}
|
||||||
</div>}
|
</div>}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.locations-card {
|
.locations-card {
|
||||||
width: 40vw;
|
width: 40vw;
|
||||||
height: 45vh;
|
height: 40vh;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
.locations-graph-card {
|
.locations-graph-card {
|
||||||
width: 40vw;
|
width: 40vw;
|
||||||
height: 45vh;
|
height: 40vh;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
.locations-card-title {
|
.locations-card-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding-top: 10px;
|
/* padding-top: 10px; */
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.resultratio-graph-card {
|
.resultratio-graph-card {
|
||||||
width: 40vw;
|
width: 40vw;
|
||||||
height: 45vh;
|
height: 40vh;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class ScatterPlot extends Component {
|
|||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
// set the dimensions and margins of the graph
|
// set the dimensions and margins of the graph
|
||||||
var margin = { top: 10, right: 30, bottom: 30, left: 60 },
|
var margin = { top: 10, right: 30, bottom: 40, left: 60 },
|
||||||
width = 460 - margin.left - margin.right,
|
width = 460 - margin.left - margin.right,
|
||||||
height = 250 - margin.top - margin.bottom;
|
height = 250 - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,38 @@
|
|||||||
.container {
|
.analytics-page::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-title::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-page {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 50vh;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 900px) {
|
@media screen and (min-width: 900px) {
|
||||||
.container {
|
.analytics-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 50vh;
|
height: 45vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,8 +82,8 @@ const TestAnalytics = () => {
|
|||||||
}, [curTestData, curTest, device, testDate]);
|
}, [curTestData, curTest, device, testDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="devices-page">
|
<div className="analytics-page">
|
||||||
<div className='title'>Analytics</div>
|
<div className='analytics-title'>Analytics</div>
|
||||||
{/* <div className='dropdown-group'>
|
{/* <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='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>
|
<button className='clear-button' onClick={toggleShowGraph}><img className="graph-icon" src={require('./graph_icon.png')}></img>{curTestData ? 'Clear' : 'Show'}</button>
|
||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
<div className='container'>
|
<div className='analytics-container'>
|
||||||
{dailyCategoryCounts && <TestAnalyticsPie data={dailyCategoryCounts} />}
|
{dailyCategoryCounts && <TestAnalyticsPie data={dailyCategoryCounts} />}
|
||||||
{dailyResults && <TestAnalyticsGraph data={dailyResults} />}
|
{dailyResults && <TestAnalyticsGraph data={dailyResults} />}
|
||||||
</div>
|
</div>
|
||||||
<div className='container'>
|
<div className='analytics-container'>
|
||||||
{allTests && <ScatterPlot data={allTests} />}
|
{allTests && <ScatterPlot data={allTests} />}
|
||||||
{allTests && <Locations />}
|
{allTests && <Locations />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.tests-count-card {
|
.tests-count-card {
|
||||||
width: 40vw;
|
width: 40vw;
|
||||||
height: 45vh;
|
height: 40vh;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
/* display: flex;
|
/* display: flex;
|
||||||
@@ -58,4 +58,18 @@
|
|||||||
font-size: x-small;
|
font-size: x-small;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 0.5em;
|
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; */
|
||||||
}
|
}
|
||||||
@@ -25,11 +25,12 @@ 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 = 700 - 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("id", "bargraph")
|
||||||
.attr("width", width + margin.left + margin.right)
|
.attr("width", width + margin.left + margin.right)
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
.append("g").attr("transform",
|
.append("g").attr("transform",
|
||||||
@@ -40,7 +41,7 @@ class TestAnalyticsGraph extends Component {
|
|||||||
yScale = d3.scaleLinear().range([height, 0]);
|
yScale = d3.scaleLinear().range([height, 0]);
|
||||||
|
|
||||||
var g = svg.append("g")
|
var g = svg.append("g")
|
||||||
.attr("transform", "translate(" + 10 + "," + 10 + ")");
|
.attr("transform", "translate(" + 5 + "," + 5 + ")");
|
||||||
|
|
||||||
|
|
||||||
xScale.domain(data.map(function (d) { return moment(d.date).format("MM-DD"); }));
|
xScale.domain(data.map(function (d) { return moment(d.date).format("MM-DD"); }));
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.category-graph-card {
|
.category-graph-card {
|
||||||
width: 40vw;
|
width: 40vw;
|
||||||
height: 45vh;
|
height: 40vh;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class TestAnalyticsPie extends Component {
|
|||||||
drawChart(data) {
|
drawChart(data) {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
var margin = { top: 30, right: 10, bottom: 15, left: 25 },
|
var margin = { top: 15, right: 10, bottom: 45, left: 10 },
|
||||||
width = 480 - margin.left - margin.right,
|
width = 480 - margin.left - margin.right,
|
||||||
height = 250 - margin.top - margin.bottom,
|
height = 250 - margin.top - margin.bottom,
|
||||||
radius = Math.min(width, height) / 2;
|
radius = Math.min(width, height) / 2;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const WorldMap = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg width={ '40vw' } height={ '50vh' } viewBox="0 0 800 450">
|
<svg width={ '40vw' } height={ '35vh' } viewBox="0 0 800 450">
|
||||||
<g className="countries">
|
<g className="countries">
|
||||||
{
|
{
|
||||||
geographies.map((d,i) => (
|
geographies.map((d,i) => (
|
||||||
|
|||||||
@@ -11,6 +11,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 Calibrations from './components/Calibrations';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -32,6 +33,10 @@ const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
path: "/map",
|
path: "/map",
|
||||||
element: <RegistraionChart />,
|
element: <RegistraionChart />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/calibrations",
|
||||||
|
element: <Calibrations />,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user