Skip to content

Commit

Permalink
Zeke/dev/typescript conversion (#64)
Browse files Browse the repository at this point in the history
## Problem
On the web side, the app uses React but is implemented using Javascript. It is recommended to use Typescript instead.

## Solution
* Convert web app to Typescript.
* Modify web project files to use Typescript, added TS configs, and removed old and conflicting project files.
* Renamed `.js` files to `.ts` or `.tsx` as needed.
* Refactored Javascript code to Typescript/JSX compliant.
* Created interfaces as necessary.
* Factored out some code in large components and pages

## Ticket URL
https://mediform.atlassian.net/browse/MEDI-17

## Documentation
N/A

## Tests Run
* Tested locally. Rebuilt `web` image and container from scratch
* Manually tested web application
    - Login
    - Create a new patient encounter form
    - Search PE forms
    - View existing PE
    - Update existing PEs
    - Delete PEs
  • Loading branch information
critch646 authored Jan 16, 2024
1 parent edf12ee commit 58956a7
Show file tree
Hide file tree
Showing 54 changed files with 1,061 additions and 5,579 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ import {
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import CancelIcon from "@mui/icons-material/Cancel";
import { ConfirmDeletionDialogProps } from "../interfaces/ConfirmDeletionDialogProps";

/**
* Renders a confirmation dialog for deleting an encounter form.
*
* @param {boolean} open - Whether the dialog is open or not.
* @param {function} onClose - Function to handle dialog close event.
* @param {function} onConfirmDelete - Function to handle delete confirmation.
* @param props - The props of the component.
*
* @returns {JSX.Element} - ConfirmDeletionDialog component.
* @returns ConfirmDeletionDialog component.
*/
export const ConfirmDeletionDialog = ({ open, onClose, onConfirmDelete }) => {
export const ConfirmDeletionDialog: React.FC<ConfirmDeletionDialogProps> = ({ open, onClose, onConfirmDelete }) => {
return (
<Dialog
open={open}
Expand Down
20 changes: 20 additions & 0 deletions app/web/components/Copyright.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';
import Typography, { TypographyProps } from '@mui/material/Typography';


/**
* This component is used to display the copy right information in the footer
*
* @param props The properties of the component
*
* @returns The JSX element for the component
*/
export function Copyright(props: TypographyProps) {
return (
<Typography variant="body2" color="text.secondary" align="center" {...props}>
{'Copyright © '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
126 changes: 126 additions & 0 deletions app/web/components/ProtectedNavbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { useState } from "react";
import Link from "next/link";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import Divider from "@mui/material/Divider";
import Drawer from "@mui/material/Drawer";
import IconButton from "@mui/material/IconButton";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import MenuIcon from "@mui/icons-material/Menu";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";

/**
* Renders a navbar for the protected routes.
*
* @returns JSX element representing the navbar.
*/
const ProtectedNavbar: React.FC = () => {
const [mobileOpen, setMobileOpen] = useState(false);

const drawerWidth = 240;

const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};

const logout = () => {
window.localStorage.removeItem("auth-token");
window.location.pathname = "/";
};

const drawer = (
<Box onClick={handleDrawerToggle} sx={{ textAlign: "center" }}>
<Typography variant="h6" sx={{ my: 2 }}>
MUI
</Typography>
<Divider />
<List>
<ListItem key="New Entry" disablePadding>
<ListItemButton href="/forms/form" sx={{ textAlign: "center" }}>
<ListItemText primary="New Entry" />
</ListItemButton>
</ListItem>
<ListItemButton onClick={logout} sx={{ textAlign: "center" }}>
<ListItemText primary="Logout" />
</ListItemButton>
</List>
</Box>
);

return (
<>
<Box sx={{ display: "flex", pb: "60px" }}>
<AppBar component="nav">
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: "none" } }}
>
<MenuIcon />
</IconButton>
<Typography
variant="h6"
component="div"
sx={{
flexGrow: 1,
display: { xs: "none", sm: "block", fontWeight: "bold" },
}}
>
<Link href="/home">Home</Link>
</Typography>
<Box sx={{ display: { xs: "none", sm: "block" } }}>
<Button
href="/search/encounters"
sx={{ color: "#fff", fontWeight: "bold" }}
>
List Entries
</Button>
<Button
href="/forms/form"
sx={{ color: "#fff", fontWeight: "bold" }}
>
New Entry
</Button>
<Button
key="logout"
sx={{ color: "#fff", fontWeight: "bold" }}
onClick={logout}
>
Logout
</Button>
</Box>
</Toolbar>
</AppBar>
<Box component="nav">
<Drawer
variant="temporary"
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
sx={{
display: { xs: "block", sm: "none" },
"& .MuiDrawer-paper": {
boxSizing: "border-box",
width: drawerWidth,
},
}}
>
{drawer}
</Drawer>
</Box>
</Box>
</>
);
};

export default ProtectedNavbar;
25 changes: 25 additions & 0 deletions app/web/components/RenderErrorAlerts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import { Alert } from "@mui/material";
import { FieldErrors, FieldError } from "react-hook-form";

export function RenderErrorAlerts(errors: FieldErrors<any>) {
return (
<>
{Object.entries(errors).map(([fieldName, error], index) => {
if (error && (error as FieldError).message) {
return (
<Alert
severity="error"
key={index}
sx={{ mt: 1 }}
variant="filled"
>
{error.message as React.ReactNode}
</Alert>
);
}
return null;
})}
</>
);
}
25 changes: 25 additions & 0 deletions app/web/components/RenderSubmitAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import { Alert } from "@mui/material";
import { AlertObject } from "../interfaces/AlertObject";

/**
* Renders a submit alert based on the provided alert object.
*
* @param alert - Alert object containing the type and message of the alert.
*
* @returns JSX element representing the submit alert, or null if no alert is provided.
*/

export function RenderSubmitAlert(
alert: AlertObject | null
): JSX.Element | null {

if (alert) {
return (
<Alert severity={alert.type} sx={{ mt: 2 }} variant="filled">
{alert.message}
</Alert>
);
}
return null;
}
9 changes: 9 additions & 0 deletions app/web/components/SlideTransition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";
import Slide from "@mui/material/Slide";

export const SlideTransition = React.forwardRef(function Transition(
props: any,
ref: React.Ref<unknown>
) {
return <Slide direction="up" ref={ref} {...props} />;
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React from "react";
import { FormControl, TextField } from "@mui/material";
import { Controller } from "react-hook-form";
import { FormFieldProps } from "../../interfaces/FormFieldProps";


/**
* Renders an age input field controlled by React Hook Form.
*
* @param {object} control - React Hook Form control object.
* @param {object} errors - Object containing form errors.
* @param props FormField component props.
*
* @returns {JSX.Element} - AgeField component.
* @returns AgeField component.
*/
export function AgeField(control, errors) {
export function AgeField({ control, errors }: FormFieldProps) {
return (
<FormControl>
<Controller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import { MobileDatePicker } from "@mui/x-date-pickers/MobileDatePicker";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import { Controller } from "react-hook-form";
import { FormFieldProps } from "../../interfaces/FormFieldProps";


/**
* Renders an arrival date input field controlled by React Hook Form.
*
* @param {object} control - React Hook Form control object.
* @param {object} errors - Object containing form errors.
* @param props Props for ArrivalDateField component.
*
* @returns {JSX.Element} - ArrivalDateField component.
* @returns ArrivalDateField component.
*/
export function ArrivalDateField(control, errors) {
export function ArrivalDateField({ control, errors }: FormFieldProps) {
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<FormControl>
Expand All @@ -27,12 +28,12 @@ export function ArrivalDateField(control, errors) {
inputFormat="MM/dd/yyyy"
value={value}
onChange={onChange}
error={Boolean(errors?.arrival_date)}
renderInput={(params) => (
<TextField
{...params}
required={true}
error={Boolean(errors?.arrival_date)}
helperText={errors.arrival_date?.message?.toString()}
/>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ import {
RadioGroup,
} from "@mui/material";
import { Controller } from "react-hook-form";
import { FormFieldProps } from "../../interfaces/FormFieldProps";


/**
* Renders an arrival method radio group controlled by React Hook Form.
*
* @param {object} control - React Hook Form control object.
* @param {object} errors - Object containing form errors.
*
* @returns {JSX.Element} - ArrivalMethodField component.
* @param props - Props for ArrivalMethodField component.
*
* @returns ArrivalMethodField component.
*/
export function ArrivalMethodField(control, errors) {
export function ArrivalMethodField({ control, errors }: FormFieldProps) {
const hasError = Boolean(errors?.arrival_method);
return (
<FormControl error={Boolean(errors?.arrival_method)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { TimePicker } from "@mui/x-date-pickers/TimePicker";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import { Controller } from "react-hook-form";
import { FormFieldProps } from "../../interfaces/FormFieldProps";

/**
* Renders an arrival time input field controlled by React Hook Form.
*
* @param {object} control - React Hook Form control object.
* @param {object} errors - Object containing form errors.
* @param props FormField component props.
*
* @returns {JSX.Element} - ArrivalTimeField component.
* @returns ArrivalTimeField component.
*/
export function ArrivalTimeField(control, errors) {
export function ArrivalTimeField({ control, errors }: FormFieldProps) {
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<FormControl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,31 @@ import {
FormLabel,
ListItemText,
MenuItem,
MenuProps,
OutlinedInput,
Select,
} from "@mui/material";
import { Controller } from "react-hook-form";
import { chiefComplaints } from "../../utils/constants";
import { FormFieldProps } from "../../interfaces/FormFieldProps";

/**
* Renders a chief complaint select field controlled by React Hook Form.
*
* @param {object} control - React Hook Form control object.
* @param {object} errors - Object containing form errors.
* @param props FormField component props.
*
* @returns {JSX.Element} - ChiefComplaintField component.
* @returns ChiefComplaintField component.
*/
export function ChiefComplaintField(control, errors) {
export function ChiefComplaintField({ control, errors }: FormFieldProps) {

const MenuProps = {
PaperProps: {
style: {
maxHeight: 300,
width: 250,
},
},
};

return (
<FormControl error={Boolean(errors?.chief_complaints)} fullWidth>
<FormLabel>Chief Complaint</FormLabel>
Expand Down Expand Up @@ -51,7 +60,7 @@ export function ChiefComplaintField(control, errors) {
)}
/>
{errors.chief_complaints && (
<FormHelperText error>{errors.chief_complaints.message}</FormHelperText>
<FormHelperText error>{String(errors.chief_complaints?.message)}</FormHelperText>
)}
</FormControl>
);
Expand Down
Loading

0 comments on commit 58956a7

Please sign in to comment.