Skip to content

Commit

Permalink
fix: loading for the library page (#3016)
Browse files Browse the repository at this point in the history
* fix: wip - loading for the library page

* fix: cleanup

* test: update snapshot

* test: add test for the loading table

* fix: simplify loading blob styles, add dark-gray

* test: update snapshots

* fix: remove debug code
  • Loading branch information
mcmcgrath13 authored Dec 10, 2024
1 parent 4ebdaef commit f6d3333
Show file tree
Hide file tree
Showing 9 changed files with 1,248 additions and 260 deletions.
281 changes: 175 additions & 106 deletions containers/ecr-viewer/src/app/components/EcrTableClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { SortButton } from "@/app/components/SortButton";
import { EcrDisplay } from "@/app/services/listEcrDataService";
import { toSentenceCase } from "@/app/services/formatService";
import { usePathname, useSearchParams, useRouter } from "next/navigation";
import { range } from "../view-data/utils/utils";
import classNames from "classnames";

const basePath =
process.env.NODE_ENV === "production" ? process.env.NEXT_PUBLIC_BASEPATH : "";
Expand All @@ -15,6 +17,22 @@ type EcrTableClientProps = {
sortDirection: string;
};

type EcrTableStyledProps = {
headers: Column[];
handleSort: SortHandlerFn;
children: React.ReactNode;
};

type Column = {
id: string;
value: string;
className: string;
dataSortable: boolean;
sortDirection: string;
};

type SortHandlerFn = (columnId: string, direction: string) => void;

interface SortPreferences {
columnId: string;
direction: string;
Expand All @@ -25,6 +43,44 @@ const defaultPreferences = {
direction: "DESC",
};

const initialHeaders = [
{
id: "patient",
value: "Patient",
className: "library-patient-column",
dataSortable: true,
sortDirection: "",
},
{
id: "date_created",
value: "Received Date",
className: "library-received-date-column",
dataSortable: true,
sortDirection: "",
},
{
id: "report_date",
value: "Encounter Date",
className: "library-encounter-date-column",
dataSortable: true,
sortDirection: "",
},
{
id: "reportable_condition",
value: "Reportable Condition",
className: "library-condition-colum",
dataSortable: false,
sortDirection: "",
},
{
id: "rule_summary",
value: "RCKMS Rule Summary",
className: "library-rule-column",
dataSortable: false,
sortDirection: "",
},
];

/**
*
* @param props - The properties passed to the component.
Expand All @@ -46,43 +102,7 @@ export const EcrTableClient: React.FC<EcrTableClientProps> = ({
useState<SortPreferences>(defaultPreferences);
const [sortedData, setSortedData] = useState<EcrDisplay[]>(data);

const [headers, setHeaders] = useState([
{
id: "patient",
value: "Patient",
className: "library-patient-column",
dataSortable: true,
sortDirection: "",
},
{
id: "date_created",
value: "Received Date",
className: "library-received-date-column",
dataSortable: true,
sortDirection: "",
},
{
id: "report_date",
value: "Encounter Date",
className: "library-encounter-date-column",
dataSortable: true,
sortDirection: "",
},
{
id: "reportable_condition",
value: "Reportable Condition",
className: "library-condition-colum",
dataSortable: false,
sortDirection: "",
},
{
id: "rule_summary",
value: "RCKMS Rule Summary",
className: "library-rule-column",
dataSortable: false,
sortDirection: "",
},
]);
const [headers, setHeaders] = useState(initialHeaders);

/**
* Updates the URL with the current sort preferences.
Expand Down Expand Up @@ -149,6 +169,37 @@ export const EcrTableClient: React.FC<EcrTableClientProps> = ({
);
};

return (
<EcrTableStyled headers={headers} handleSort={handleSort}>
{sortedData.map((item, index) => {
return <DataRow key={index} item={item} />;
})}
</EcrTableStyled>
);
};

/**
* The Ecr Library table, but with blobs instead of data.
* @returns - The JSX element representing the eCR table.
*/
export const EcrTableLoading = () => {
return (
<EcrTableStyled headers={initialHeaders} handleSort={() => {}}>
{range(10).map((i) => {
return (
<BlobRow key={i} themeColor={i % 2 == 0 ? "gray" : "dark-gray"} />
);
})}
</EcrTableStyled>
);
};

// EcrTable without any logic or state.
const EcrTableStyled: React.FC<EcrTableStyledProps> = ({
headers,
handleSort,
children,
}) => {
return (
<div className="ecr-library-wrapper width-full overflow-auto">
<Table
Expand All @@ -161,90 +212,70 @@ export const EcrTableClient: React.FC<EcrTableClientProps> = ({
>
<thead className={"position-sticky top-0"}>
<tr>
{headers.map((column) =>
column.sortDirection ? (
<th
id={`${column.id}-header`}
key={`${column.value}`}
scope="col"
role="columnheader"
className={column.className}
data-sortable={column.dataSortable}
aria-sort={getAriaSortValue(column.sortDirection)}
>
<div className="sort-div">
{column.value}
<SortButton
columnName={column.id}
className={
column.sortDirection === "ASC"
? "sortable-asc-column"
: "sortable-desc-column"
}
direction={column.sortDirection}
handleSort={handleSort}
></SortButton>
</div>
</th>
) : (
<th
id={`${column.id}-header`}
key={`${column.value}`}
scope="col"
role="columnheader"
className={column.className}
>
<div className={"display-flex"}>
{column.value}
{column.dataSortable ? (
<SortButton
columnName={column.id}
className={"sortable-column"}
direction={column.sortDirection}
handleSort={handleSort}
></SortButton>
) : null}
</div>
</th>
),
)}
{headers.map((column) => (
<Header key={column.id} column={column} handleSort={handleSort} />
))}
</tr>
</thead>
<tbody>{renderListEcrTableData(sortedData)}</tbody>
<tbody>{children}</tbody>
</Table>
</div>
);
};

const Header = ({
column,
handleSort,
}: {
column: Column;
handleSort: SortHandlerFn;
}) => {
return (
<th
id={`${column.id}-header`}
key={`${column.value}`}
scope="col"
role="columnheader"
className={column.className}
data-sortable={column.dataSortable}
aria-sort={getAriaSortValue(column.sortDirection)}
>
<div className={column.sortDirection ? "sort-div" : "display-flex"}>
{column.value}
{(column.sortDirection || column.dataSortable) && (
<SortButton
columnName={column.id}
className={classNames({
"sortable-asc-column": column.sortDirection === "ASC",
"sortable-desc-column": column.sortDirection === "DESC",
"sortable-column": column.sortDirection === "",
})}
direction={column.sortDirection}
handleSort={handleSort}
></SortButton>
)}
</div>
</th>
);
};

type AriaSortType = "none" | "ascending" | "descending" | "other";

const getAriaSortValue = (sortDirection: string): AriaSortType => {
if (sortDirection !== "") {
return sortDirection === "ASC" ? "ascending" : "descending";
} else {
return "none";
const getAriaSortValue = (sortDirection: string): AriaSortType | undefined => {
if (sortDirection === "ASC") {
return "ascending";
} else if (sortDirection === "DESC") {
return "descending";
}
};

/**
* Renders table rows given a list of eCRs. Each row contains an eCR ID linked to its
* individual eCR viewer page and the stor ed date.
* @param listFhirData - The list of eCRs to render.
* @returns An array of JSX table row elements representing the list of eCRs.
*/
const renderListEcrTableData = (listFhirData: EcrDisplay[]) => {
return listFhirData.map((item, index) => {
return formatRow(item, index);
});
};

/**
* Formats a single row of the eCR table.
* @param item - The eCR data to be formatted.
* @param index - The index of the eCR data in the list.
* @param props - The properties passed to the component.
* @param props.item - The eCR data to be formatted.
* @returns A JSX table row element representing the eCR data.
*/
const formatRow = (item: EcrDisplay, index: number) => {
const DataRow = ({ item }: { item: EcrDisplay }) => {
const patient_first_name = toSentenceCase(item.patient_first_name);
const patient_last_name = toSentenceCase(item.patient_last_name);
const createDateObj = new Date(item.date_created);
Expand All @@ -271,7 +302,7 @@ const formatRow = (item: EcrDisplay, index: number) => {
);

return (
<tr key={`table-row-${index}`}>
<tr>
<td>
<a href={`${basePath}/view-data?id=${item.ecrId}`}>
{patient_first_name} {patient_last_name}
Expand All @@ -295,6 +326,44 @@ const formatRow = (item: EcrDisplay, index: number) => {
);
};

const Blob = ({ themeColor }: { themeColor: string }) => {
return (
<div className="grid-row">
<div
className={`loading-blob grid-col-8 loading-blob-${themeColor} width-full`}
>
&nbsp;
</div>
</div>
);
};

const BlobRow = ({ themeColor }: { themeColor: string }) => {
return (
<tr>
<td>
<Blob themeColor={themeColor} />
</td>
<td>
<Blob themeColor={themeColor} />
<br />
<Blob themeColor={themeColor} />
</td>
<td>
<Blob themeColor={themeColor} />
<br />
<Blob themeColor={themeColor} />
</td>
<td>
<Blob themeColor={themeColor} />
</td>
<td>
<Blob themeColor={themeColor} />
</td>
</tr>
);
};

/**
* Formats a date object to a string in the format MM/DD/YYYY.
* @param date - The date object to be formatted.
Expand Down
21 changes: 12 additions & 9 deletions containers/ecr-viewer/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from "react";
import React, { Suspense } from "react";
import Header from "./Header";
import { getTotalEcrCount } from "@/app/services/listEcrDataService";
import EcrPaginationWrapper from "@/app/components/EcrPaginationWrapper";
import EcrTable from "@/app/components/EcrTable";
import LibrarySearch from "./components/LibrarySearch";
import NotFound from "./not-found";
import { EcrTableLoading } from "./components/EcrTableClient";

/**
* Functional component for rendering the home page that lists all eCRs.
Expand Down Expand Up @@ -44,14 +45,16 @@ const HomePage = async ({
/>
</div>
<EcrPaginationWrapper totalCount={totalCount}>
<EcrTable
currentPage={currentPage}
itemsPerPage={itemsPerPage}
sortColumn={sortColumn}
sortDirection={sortDirection}
searchTerm={searchTerm}
filterConditions={filterConditions}
/>
<Suspense fallback={<EcrTableLoading />}>
<EcrTable
currentPage={currentPage}
itemsPerPage={itemsPerPage}
sortColumn={sortColumn}
sortDirection={sortDirection}
searchTerm={searchTerm}
filterConditions={filterConditions}
/>
</Suspense>
</EcrPaginationWrapper>
</main>
</div>
Expand Down
Loading

0 comments on commit f6d3333

Please sign in to comment.