19 Commits

Author SHA1 Message Date
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
52 changed files with 4060 additions and 584 deletions

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

@@ -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: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

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,35 @@
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>
<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

@@ -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-preprod.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;

View File

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

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

@@ -0,0 +1,28 @@
.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;
}

View File

@@ -0,0 +1,106 @@
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-preprod.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-preprod.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="350umol kmon04" onChange={onNameChange} />
</label>
<div className='dates'>
<div className='button' onClick={() => createUser()}>Create User</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

@@ -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 dashboard
</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,28 @@ import { getAnalytics } from "firebase/analytics";
// measurementId: "G-6JHQ47S9DD"
// };
/* 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

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

@@ -0,0 +1,66 @@
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 } from '@mui/icons-material';
import ExportData from '../components/ExportData';
import Registration from '../components/Registration';
import { FaAndroid, FaPhone, FaUser } from 'react-icons/fa';
import AppInstallation from '../components/AppInstallation';
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: "Data Download",
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 />
}
},
]
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
}
});