Skip to content

Commit

Permalink
Merge pull request #22 from ubclaunchpad/rafael-backend-frontend
Browse files Browse the repository at this point in the history
Integrated the frontend & backend for logbook page and log history page.
  • Loading branch information
parkrafael authored Jan 30, 2025
2 parents cf9508e + d0afbb3 commit 72bdbf2
Show file tree
Hide file tree
Showing 19 changed files with 298 additions and 68 deletions.
65 changes: 65 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Clinical Logging API Endpoints
**Note:** You need to generate a token before you can access all endpoints either by logging in using the front-end interface or using the auth endpoint.

## Auth
### Generate token
`POST` api/auth/token

Query Params:
| Name | Type |
|------|------|
| **email** | string |
| **password** | string |

## Logbooks
### Create logbook
`POST` api/logbooks/

Query Params:
| Name | Type |
|------|------|
| **type** | string |
| **title** | string |

### Logbook details
`GET` api/logbooks/{logbookID}

Query Params:
| Name | Type |
|------|------|
| **logbookID** | uuid |

### User's logbooks
`GET` api/logbooks/

Query Params: None

## Logs
### Create log
`POST` api/logbooks/{logbookID}/logs

Query Params: Params changes between log types. You can find example queries for each log type in the `examples` directory.

### Logbook's logs
`GET` api/logbooks/{logbookID}/logs

Query Params:
| Name | Type |
|------|------|
| **logbookID** | uuid |

### Log details
`GET` api/logbooks/{logbookID}/logs/{logID}

Query Params:
| Name | Type |
|------|------|
| **logbookID** | uuid |
| **logID** | uuid |







52 changes: 52 additions & 0 deletions backend/examples/adult_cardiac_logs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"case_no": 12345,
"type": "adult_cardiac_logs",
"patient_id": "P123456789",
"age": 68,
"surgeon": "Dr. Emily Thompson",
"or_date": "2025-01-15T09:30:00+00:00",
"reason_for_referral": "Severe angina, left main coronary artery disease",
"hpi": "Patient presents with chronic angina, worsening over the past 3 months. Non-responsive to medical management.",
"phmx": "Chronic obstructive pulmonary disease (COPD), hypertension, hyperlipidemia, previous myocardial infarction (2 years ago)",
"gender": "Female",
"social_etoh": "Retired, lives with spouse, occasional alcohol use, non-smoker",
"social_smoking": "Never smoked",
"social_drugs": "No history of illicit drug use",
"social_allergies": "No known drug allergies",
"medicine": "Aspirin 81mg, Metoprolol 25mg, Simvastatin 40mg, Isosorbide dinitrate 10mg",
"exam_weight": 72.0,
"exam_height": 160,
"exam_bmi": 28.1,
"exam_veins": "Veins are normal, no visible varicosities or thrombosis",
"exam_allen_test": "Negative for both radial and ulnar patency",
"exam_pulses_1": "Normal, 2+",
"exam_pulses_2": "Normal, 2+",
"exam_pulses_3": "Diminished, 1+",
"exam_pulses_4": "Normal, 2+",
"invx_echo": "Moderate left ventricular dysfunction, ejection fraction 40%, mild aortic stenosis",
"invx_hb": 12.5,
"invx_w": 5.4,
"invx_plt": 220,
"invx_cr_1": 1.1,
"invx_cr_2": 1.0,
"invx_cr_3": 1.2,
"invx_cr_4": 1.1,
"invx_cr_5": 1.0,
"invx_cr_6": 1.0,
"cath_image": "https://example.com/cath_image_left_main_stenosis.jpg",
"cath_text": "Significant stenosis in the left main coronary artery (70%).",
"cxr": "No acute lung pathology, cardiomegaly present.",
"ct_image": "https://example.com/ct_image_coronary_calcification.jpg",
"ct_text": "CT coronary angiography shows severe calcification in the proximal left anterior descending artery.",
"surgical_plan": "Plan for coronary artery bypass grafting (CABG) involving left internal mammary artery (LIMA) to LAD and saphenous vein graft to RCA.",
"first_operator_flag": "Yes",
"or_flag": "Completed",
"issue_flag": "Follow-up scheduled for 1 week",
"fu_flag": "Follow-up readiness confirmed, no complications post-op",
"op_notes_cpb": "CPB initiated with aortic cannulation and venous drainage via right atrium. Heparinization achieved with 300 units/kg.",
"op_notes_xc": "Ascending aorta cross-clamped at 20 minutes into the procedure.",
"op_notes_ca": "CABG performed, 2 grafts placed with good flow noted to both LAD and RCA.",
"my_role": "Senior resident assisting in bypass graft placement and surgical closure",
"post_op_course": "Stable in the ICU, extubated 6 hours post-op, no arrhythmias or complications.",
"learning_points": "Close monitoring of hemodynamics during CPB; careful handling of the left internal mammary artery for grafting."
}
6 changes: 4 additions & 2 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import dotenv from "dotenv";
import express from "express";
import authRoutes from "./src/routes/auth-route.js";
import logbookRoutes from "./src/routes/logbooks-route.js";
import logsRoutes from "./src/routes/logs-route.js";
import transcriptionRoutes from "./src/routes/transcription-route.js";
import fileUpload from "express-fileupload";

dotenv.config();

const corsOptions = {
origin: ["http://localhost:5173"],
origin: ["http://localhost:5173"]
};
const app = express();
const PORT = process.env.PORT || 8080;
Expand All @@ -18,9 +19,10 @@ app.use(cors(corsOptions));
app.use(express.json());
app.use(fileUpload());

//Routes
// Routes
app.use("/api/auth", authRoutes);
app.use("/api/logbooks", logbookRoutes);
app.use("/api/logs", logsRoutes);
app.use("/api/transcriptions", transcriptionRoutes);

app.listen(PORT, () => {
Expand Down
32 changes: 29 additions & 3 deletions backend/src/routes/auth-route.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
import express from "express";
import auth from "../middlewares/auth.js";
import dotenv from "dotenv";
import { createClient } from "@supabase/supabase-js";

dotenv.config();

const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_ANON_KEY;

const router = express.Router();
const supabase = createClient(supabaseUrl, supabaseKey);

router.post("/check", auth, async (req, res) => {
res.json({ message: "Auth Check Ok"});
// remove for production
router.post("/token", async (req, res) => {
try {
const { email, password } = req.body;
if (!email) {
throw new Error("Email is missing");
}
if (!password) {
throw new Error("Password is missing");
}
const { data, error } = await supabase.auth.signInWithPassword({
email: email,
password: password,
});
if (error) {
throw new Error(error.message);
}
res.json({ data: data.session.access_token })
} catch (error) {
res.json({ error: error.message })
}
})

export default router;
16 changes: 16 additions & 0 deletions backend/src/routes/logs-route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import express from "express";
import auth from "../middlewares/auth.js";
import { getUserLogs } from "../services/logs-service.js";

const router = express.Router();

router.get("", auth, async (req, res) => {
const userLogs = await getUserLogs(req);
if (userLogs.error) {
res.status(500).json({ error: userLogs.error });
} else {
res.status(200).json({ data: userLogs });
}
})

export default router;
16 changes: 12 additions & 4 deletions backend/src/services/logbooks-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function getLogbook(req) {
const { logbookID } = req.params;
const logbook = await getTable(supabase, "logbooks", "id", logbookID, "resource");
if (typeof logbook == "undefined") {
throw new Error(`logbook ${logbookID} does not exist`);
throw new Error(`Logbook ${logbookID} does not exist`);
}
return logbook;
} catch (error) {
Expand All @@ -50,13 +50,21 @@ export async function createLog(req) {
throw new Error(logbookType.error);
}
if (body["type"] !== logbookType) {
throw new Error(`log type '${body["type"]}' does not match logbook type '${logbookType}'`);
throw new Error(`Log type '${body["type"]}' does not match logbook type '${logbookType}'`);
}
switch (body["type"]) {
case "adult_cardiac_logs":
return await insertTable(supabase, "adult_cardiac_logs", body);
case "congenital_surgery_logs":
return await insertTable(supabase, "congenital_surgery_logs", body);
case "general_surgery_logs":
return await insertTable(supabase, "general_surgery_logs", body);
case "gyn_logs":
return await insertTable(supabase, "gyn_logs", body)
case "ob_logs":
return await insertTable(supabase, "ob_logs", body)
default:
throw new Error(`log and logbook type '${body["type"]}' are invalid`);
throw new Error(`Log and logbook type '${body["type"]}' are invalid`);
}
} catch (error) {
return { error: error.message };
Expand Down Expand Up @@ -88,7 +96,7 @@ export async function getLog(req) {
}
const log = await getTable(supabase, logbookType, "id", logID, "resource");
if (typeof log == "undefined") {
throw new Error(`log ${logID} does not exist`);
throw new Error(`Log ${logID} does not exist`);
}
return log;
} catch (error) {
Expand Down
20 changes: 20 additions & 0 deletions backend/src/services/logs-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import getTable from "../utils/get-table.js";
import { getUserLogbooks } from "./logbooks-service.js";

export async function getUserLogs(req) {
try {
const supabase = req.supabase;
const userLogbooks = await getUserLogbooks(req);
if (userLogbooks.error) {
throw new Error(userLogbooks.error)
}
const logs = [];
for (const logbook of userLogbooks) {
const logbookLogs = await getTable(supabase, logbook.type, "logbook_id", logbook.id, "collection");
logs.push(...logbookLogs)
}
return logs;
} catch (error) {
return { error: error.message };
}
}
2 changes: 1 addition & 1 deletion backend/src/utils/get-logbook-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export default async function getLogbookType(logbookID, supabase) {
try {
const { data, error } = await supabase.from("logbooks").select().eq("id", logbookID);
if (data.length == 0) {
throw new Error(`logbook ${logbookID} does not exist`);
throw new Error(`Logbook ${logbookID} does not exist`);
} else if (error) {
throw new Error(error.message);
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/utils/get-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default async function getTable(supabase, table, param, value, type) {
} else if (param !== null && value !== null) {
({ data, error } = await supabase.from(table).select().eq(param, value));
} else {
throw new Error(`${param == null ? "param" : "value"} is empty at getTable`);
throw new Error(`${param == null ? "Param" : "Value"} is empty at getTable`);
}
if (error) {
throw new Error(error.message);
Expand Down
8 changes: 4 additions & 4 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@mui/material": "^6.1.2",
"@mui/x-date-pickers": "^7.23.0",
"@supabase/supabase-js": "^2.45.5",
"axios": "^1.7.7",
"axios": "^1.7.9",
"pdfjs-dist": "^4.7.76",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/LogHistory/LogTable.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ChevronUpDownIcon } from "@heroicons/react/24/outline";
import "./LogTable.css";
import formatDate from "../../utils/helpers/formatDate";
import formatType from "../../utils/helpers/formatType";

export default function LogTable({
currentLogs,
Expand Down Expand Up @@ -41,8 +43,8 @@ export default function LogTable({
/>
</td>
<td className="log-title-column title-column">{log.title}</td>
<td className="type-column">{log.type}</td>
<td className="date-column">{log.dateCreated}</td>
<td className="type-column">{formatType(log.type)}</td>
<td className="date-column">{formatDate(log.created_at)}</td>
</tr>
))}
</tbody>
Expand Down
15 changes: 10 additions & 5 deletions frontend/src/components/Logbooks/LogbookCard.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { LogbookTypeInfo } from "./LogbookTypeInfo";
import LogRectangle from "../../assets/images/LogRectangle.png";
import "./LogbookCard.css";
import formatType from "../../utils/helpers/formatType"
import formatDate from "../../utils/helpers/formatDate";

export default function LogbookCard({ title, type, storage, created }) {
const formattedType = formatType(type);
const formattedDate = formatDate(created);

/** Retrieve type information from the mapping */
const typeInfo = LogbookTypeInfo[type] || {};
const typeInfo = LogbookTypeInfo[formattedType] || {};

/** Construct class name */
const className = ["logbook-card", typeInfo.className]
Expand All @@ -17,21 +22,21 @@ export default function LogbookCard({ title, type, storage, created }) {
return (
<div className={className}>
<div className="book-cover">
<img src={bookImage} alt={type} className="book-cover-image" />
<img src={bookImage} alt={formattedType} className="book-cover-image" />
</div>
<div className="details-container">
<img src={LogRectangle} alt="" className="log-rectangle" />
<div className="book-details">
<h3 className="book-title">{title}</h3>
<div className="type-label">
Type: <span className="type-value">{type}</span>
Type: <span className="type-value">{formattedType}</span>
</div>
<div className="storage-info">
Storage: <span className="storage-count">{storage}</span>/100 logs
Storage: <span className="storage-count">{storage}</span>/ 100 logs
used
</div>
<div className="created-date">
<strong>Created</strong> {created}
<strong>Created</strong> {formattedDate}
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 72bdbf2

Please sign in to comment.