Skip to content

Commit

Permalink
Usage/reports component and overall dashboard layout. (PP-1534) (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
tdilauro authored Aug 22, 2024
1 parent 1d224cc commit f54d231
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 111 deletions.
11 changes: 9 additions & 2 deletions src/components/ContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import { FeatureFlags, PathFor } from "../interfaces";
import Admin from "../models/Admin";
import PathForProvider from "@thepalaceproject/web-opds-client/lib/components/context/PathForContext";
import ActionCreator from "../actions";
import AppContextProvider from "../context/appContext";
import AppContextProvider, { AppContextType } from "../context/appContext";

// Note: Not all elements of these props make it into the `ContextProvider`.
// Some are exposed only through the `AppContextProvider` component (which
// this component wraps.
// TODO: We should get this interface to the point where we can just extend
// the `ConfigurationSettings` interface.
export interface ContextProviderProps extends React.Props<ContextProvider> {
store?: Store<RootState>;
csrfToken: string;
Expand All @@ -19,6 +24,7 @@ export interface ContextProviderProps extends React.Props<ContextProvider> {
library?: string;
}[];
featureFlags: FeatureFlags;
quicksightPagePath?: string;
}

/** Provides a redux store, configuration options, and a function to create URLs
Expand Down Expand Up @@ -97,11 +103,12 @@ export default class ContextProvider extends React.Component<
}

render() {
const appContextValue = {
const appContextValue: AppContextType = {
csrfToken: this.props.csrfToken,
settingUp: this.props.settingUp,
admin: this.admin,
featureFlags: this.props.featureFlags,
quicksightPagePath: this.props.quicksightPagePath,
};
return (
<PathForProvider pathFor={this.pathFor}>
Expand Down
69 changes: 46 additions & 23 deletions src/components/LibraryStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import StatsTotalCirculationsGroup from "./StatsTotalCirculationsGroup";
import StatsPatronGroup from "./StatsPatronGroup";
import StatsInventoryGroup from "./StatsInventoryGroup";
import StatsCollectionsGroup from "./StatsCollectionsGroup";
import StatsUsageReportsGroup from "./StatsUsageReportsGroup";
import { useAppContext } from "../context/appContext";

export interface LibraryStatsProps {
stats: LibraryStatistics;
Expand Down Expand Up @@ -43,42 +45,63 @@ const LibraryStats = ({ stats, library }: LibraryStatsProps) => {
const inventoryReportRequestEnabled = useMayRequestInventoryReports({
library,
});
const dashboardTitle = library
? `${libraryName || libraryKey} Dashboard`
: ALL_LIBRARIES_HEADING;
const libraryOrLibraries = library ? "library's" : "libraries'";
const quicksightPageUrl = useAppContext().quicksightPagePath;

let statsLayoutClass: string, dashboardTitle: string, implementation: string;
if (library) {
dashboardTitle = `${libraryName || libraryKey} Dashboard`;
statsLayoutClass = "stats-with-library";
implementation = "library's implementation";
} else {
dashboardTitle = ALL_LIBRARIES_HEADING;
statsLayoutClass = "stats-without-library";
implementation = "libraries' implementations";
}
return (
<div className="library-stats">
<h2>{dashboardTitle}</h2>
<ul className="stats">
<li className="stat-group">
<ul className={`stats ${statsLayoutClass}`}>
<li className="stat-group stat-patrons-group">
<StatsPatronGroup
withActiveLoan={patrons.withActiveLoan}
withActiveLoanOrHold={patrons.withActiveLoanOrHold}
heading="Current Circulation Activity"
description="Real-time patron circulation information of the Palace System."
/>
</li>
<li className="stat-group">
<StatsTotalCirculationsGroup
{...patrons}
heading="Circulation Totals"
/>
</li>
<li className="stat-group">
<StatsInventoryGroup
library={library}
inventory={inventory}
inventoryReportsEnabled={inventoryReportRequestEnabled}
/>
</li>
<li className="stat-group stat-group-wide">
{!!library && (
<li className="stat-group stat-usage-reports-group">
<StatsUsageReportsGroup
library={library}
inventoryReportsEnabled={inventoryReportRequestEnabled}
usageDataTarget="_blank" // open in new tab or window
usageDataHref={quicksightPageUrl}
/>
</li>
)}
{!library && (
<>
<li className="stat-group stat-circulation-reports-group">
<StatsTotalCirculationsGroup
{...patrons}
heading="Circulation Totals"
/>
</li>
<li className="stat-group stat-inventory-reports-group">
<StatsInventoryGroup
library={library}
inventory={inventory}
inventoryReportsEnabled={inventoryReportRequestEnabled}
/>
</li>
</>
)}
<li className="stat-group stat-collections-group">
<StatsCollectionsGroup
heading={"Configured Collections"}
description={`
The following collections are configured in your ${libraryOrLibraries}
implementation of the Palace system and are available to your users
through the Palace app.
The following collections are configured in your ${implementation} of
the Palace system and are available to your users through the Palace app.
`}
collections={collections}
showBarChart={showBarChart}
Expand Down
84 changes: 84 additions & 0 deletions src/components/StatsUsageReportsGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React = require("react");
import { Button } from "library-simplified-reusable-components";
import StatsGroup from "./StatsGroup";
import { InventoryStatistics } from "../interfaces";
import InventoryReportRequestModal from "./InventoryReportRequestModal";
import { useState } from "react";
import { useAppContext } from "../context/appContext";

type Props = {
heading?: string;
description?: string;
inventoryReportsEnabled: boolean;
library?: string;
usageDataHref?: string;
usageDataLabel?: string;
usageDataTarget?: string;
};

const StatsUsageReportsGroup = ({
heading = "Usage and Reports",
description = `
Access historical circulation and usage data of the Palace system
and request inventory and holds reports to be sent via email.
`,
usageDataHref = "https://thepalaceproject.org",
usageDataLabel = "View Usage",
usageDataTarget = "_self",
inventoryReportsEnabled,
library = undefined,
}: Props) => {
const [showReportForm, setShowReportForm] = useState(false);

return (
<>
{inventoryReportsEnabled && library && (
<InventoryReportRequestModal
show={showReportForm}
onHide={() => setShowReportForm(false)}
library={library}
/>
)}
<ul className="stat-usage-reports">
<li>
<StatsGroup heading={heading} description={description}>
<>
{inventoryReportsEnabled && library && (
<Button
callback={(() => setShowReportForm(true)) as any}
content={
<>
Request Report &nbsp;&nbsp;
<i className="fa fa-regular fa-envelope" />
</>
}
title="Request an inventory report."
disabled={showReportForm}
/>
)}
<div className="stat-group-description">
These reports provide up-to-date data on both inventory and
holds for library at the time of the request.
</div>
</>
</StatsGroup>
</li>
<li>
<div className="stat-link">
<a
href={usageDataHref}
target={usageDataTarget}
rel="noopener noreferrer"
>
{usageDataLabel}
</a>
&nbsp;&nbsp;
<i className="fa fa-external-link" />
</div>
</li>
</ul>
</>
);
};

export default StatsUsageReportsGroup;
1 change: 1 addition & 0 deletions src/context/appContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type AppContextType = {
settingUp: boolean;
admin: Admin;
featureFlags: FeatureFlags;
quicksightPagePath: string;
};

// Don't export this, since we always want the error handling behavior of our hook.
Expand Down
8 changes: 6 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class CirculationAdmin {
const div = document.createElement("div");
div.id = "opds-catalog";
div.className = "palace";
config.featureFlags = { ...defaultFeatureFlags, ...config.featureFlags };
document.getElementsByTagName("body")[0].appendChild(div);

const catalogEditorPath =
Expand All @@ -36,10 +35,15 @@ class CirculationAdmin {
"/admin/web/lists(/:library)(/:editOrCreate)(/:identifier)";
const lanePagePath =
"/admin/web/lanes(/:library)(/:editOrCreate)(/:identifier)";
const quicksightPagePath = "/admin/web/quicksight";

const queryClient = new QueryClient();

const store = buildStore();

config.featureFlags = { ...defaultFeatureFlags, ...config.featureFlags };
config.quicksightPagePath = quicksightPagePath;

const appElement = "opds-catalog";
const app = config.settingUp ? (
<Provider store={store}>
Expand All @@ -63,7 +67,7 @@ class CirculationAdmin {
component={DashboardPage}
/>
<Route
path="/admin/web/quicksight"
path={quicksightPagePath}
component={QuicksightDashboardPage}
/>
<Route
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export interface ConfigurationSettings {
tos_link_href?: string;

featureFlags: FeatureFlags;

/** `quickSightPagePath` contains the URL to the QuickSight dashboard page.
Currently, this value does not change, so we can share it via fixed config. */
quicksightPagePath: string;
}

export interface FeatureFlags {
Expand Down
69 changes: 54 additions & 15 deletions src/stylesheets/stats.scss
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
.stats-with-library {
// With library, we show a two-column layout.
grid-template-columns: repeat(2, minmax(27rem, 1fr));
grid-template-areas:
"patrons usage-reports"
"collections collections"
;
}

.stats-without-library {
// Without a library ("all libraries"), we show three columns.
grid-template-columns: repeat(3, minmax(27rem, 1fr));
grid-template-areas:
"patrons circulations inventory"
"collections collections collections"
;
}

.stats {
display: grid;
grid-gap: 1rem 1rem;
grid-template-columns: repeat(auto-fit, minmax(27rem, 1fr));
grid-auto-rows: auto;
padding: 0;

.stat-group {
display: grid;
background-color: $pagecolorlight;
border-radius: 20px;
padding: 10px;
overflow-x: scroll;

&.stat-group-wide {
grid-column-start: 1;
grid-column-end: -1;
&.stat-patrons-group {
grid-area: patrons;
}

&.stat-circulations-group {
grid-area: circulations;
}

&.stat-inventory-group {
grid-area: inventory;
}

&.stat-usage-reports-group {
grid-area: usage-reports;
}

&.stat-collections-group {
grid-area: collections;

.recharts-wrapper {
// needed for Rechart ResponsiveContainer to shrink performantly with parent
Expand All @@ -39,13 +71,29 @@

.stat-group-description {
margin: 10px;
margin-top: 0px;
margin-top: 0;
margin-bottom: 20px;
text-align: left;
font-style: italic;
font-size: small;
}

.stat-usage-reports {
display: grid;
grid-gap: 2rem;
grid-template-columns: 10fr minmax(8rem, 1fr);
list-style-type: none;
}

.stat-link {
margin-top: 1.5rem;
color: $blue-dark;

a {
color: $blue-dark;
text-decoration: underline;
}
}

ul {
padding: 0;
Expand All @@ -57,15 +105,6 @@
margin: 10px;
margin-bottom: 2px;
}
h4 {
margin: 10px;
margin-top: 0px;
margin-bottom: 20px;
text-align: left;
font-style: italic;
font-size: small;
}

}

.stat-grouping-label {
Expand Down
Loading

0 comments on commit f54d231

Please sign in to comment.