Skip to content

Commit

Permalink
Merge pull request #570 from commons-stack/ui/analytics_dark_mode
Browse files Browse the repository at this point in the history
UI/analytics dark mode
  • Loading branch information
kristoferlund authored Sep 1, 2022
2 parents 7ffe7b2 + 9032455 commit 2e85d1e
Show file tree
Hide file tree
Showing 16 changed files with 149 additions and 85 deletions.
25 changes: 25 additions & 0 deletions packages/frontend/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useErrorBoundary } from 'use-error-boundary';
import { ErrorPlaceholder } from '@/components/ErrorPlaceholder';

interface ErrorBoundaryProps {
height: number;
children: JSX.Element;
onError?: JSX.Element;
}

export const ErrorBoundary = ({
height,
children,
onError,
}: ErrorBoundaryProps): JSX.Element => {
const { ErrorBoundary } = useErrorBoundary();

return (
<ErrorBoundary
render={(): JSX.Element => children}
renderError={(): JSX.Element =>
onError || <ErrorPlaceholder height={height} />
}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

interface ErrorPlaceholderProps {
height: number;
error?: Error;
}
export const ErrorPlaceholder = ({
height,
error,
}: ErrorPlaceholderProps): JSX.Element => {
return (
<div
className="flex items-center justify-center w-full bg-warm-gray-100 rounded-xl"
className="flex items-center justify-center w-full h-full bg-warm-gray-100 rounded-xl dark:bg-slate-500"
style={{ height }}
>
<FontAwesomeIcon icon={faTriangleExclamation} size="2x" />
{error && <div>{error.message}</div>}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoaderSpinner } from '../ui/LoaderSpinner';
import { LoaderSpinner } from './ui/LoaderSpinner';

interface LoadPlaceholderProps {
height: number;
Expand All @@ -8,7 +8,7 @@ export const LoadPlaceholder = ({
}: LoadPlaceholderProps): JSX.Element => {
return (
<div
className="flex items-center w-full bg-warm-gray-100 rounded-xl"
className="flex items-center w-full bg-warm-gray-100 rounded-xl dark:bg-slate-500"
style={{ height }}
>
<LoaderSpinner />
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/components/ui/LoaderSpinner.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { faPrayingHands } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

export const LoaderSpinner = (): JSX.Element => {
return (
<div className="w-full text-center">
<FontAwesomeIcon
icon={faSpinner}
icon={faPrayingHands}
size="1x"
spin
className="inline-block"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import React, { Suspense } from 'react';
import { useParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { PeriodStatusType } from 'api/dist/period/types';
import { LoadPlaceholder } from '@/components/analytics/LoadPlaceholder';
import { PeriodPageParams, SinglePeriod } from '@/model/periods';
import { ErrorBoundary } from '@/components/ErrorBoundary';
import { LoadPlaceholder } from '@/components/LoadPlaceholder';
import { Top10Praise } from './analytics/Top10Praise';
import { ReceiversByScore } from './analytics/ReceiversByScore';
import { PeriodStats } from './analytics/PeriodStats';
Expand All @@ -16,6 +17,27 @@ import { GiversByNumber } from './analytics/GiversByNumber';
import { QuantifiersByScore } from './analytics/QuantifiersByScore';
import { ScoreDistribution } from './analytics/ScoreDistribution';

interface AnalyticsBoxProps {
height: number;
analysis: JSX.Element;
}

const AnalyticsBox = ({ height, analysis }: AnalyticsBoxProps): JSX.Element => {
return (
<ErrorBoundary height={height}>
<Suspense
fallback={
<div style={{ height }}>
<LoadPlaceholder height={height} />
</div>
}
>
{analysis}
</Suspense>
</ErrorBoundary>
);
};

const Analytics = (): JSX.Element => {
const { periodId } = useParams<PeriodPageParams>();
const period = useRecoilValue(SinglePeriod(periodId));
Expand All @@ -29,80 +51,69 @@ const Analytics = (): JSX.Element => {
return (
<div className="flex flex-col gap-2 px-5">
<h2>Period metrics</h2>
<Suspense fallback={<LoadPlaceholder height={100} />}>
<PeriodStats />
</Suspense>
<AnalyticsBox height={100} analysis={<PeriodStats />} />

<h2>Top 10 praise</h2>
<p>
Which were the ten most significant contributions this period (according
to the praise score)?
</p>
<Suspense fallback={<LoadPlaceholder height={1400} />}>
<Top10Praise />
</Suspense>
<AnalyticsBox height={1400} analysis={<Top10Praise />} />

<h2>Receivers by score</h2>
<p>Which users received the highest total praise score?</p>
<Suspense fallback={<LoadPlaceholder height={375} />}>
<ReceiversByScore />
</Suspense>
<AnalyticsBox height={375} analysis={<ReceiversByScore />} />

<h2>Receivers by number</h2>
<p>Which users received the most praise?</p>
<Suspense fallback={<LoadPlaceholder height={375} />}>
<ReceiversByNumber />
</Suspense>
<AnalyticsBox height={375} analysis={<ReceiversByNumber />} />

<h2>Givers by score</h2>
<p>
Which givers praised contributions that led to the highest praise
scores?
</p>
<Suspense fallback={<LoadPlaceholder height={375} />}>
<GiversByScore />
</Suspense>
<AnalyticsBox height={375} analysis={<GiversByScore />} />

<h2>Top givers by number</h2>
<p>Which users gave the most praise?</p>
<Suspense fallback={<LoadPlaceholder height={375} />}>
<GiversByNumber />
</Suspense>
<AnalyticsBox height={375} analysis={<GiversByNumber />} />

<h2>Quantification score distribution</h2>
<p>How often does each score of the scale get used by quantifiers?</p>
<Suspense fallback={<LoadPlaceholder height={375} />}>
<ScoreDistribution />
</Suspense>
<AnalyticsBox height={375} analysis={<ScoreDistribution />} />
<p className="text-xs">
<b>*</b> This metric disregards scores of praise marked as a duplicate,
since the score of the original is already being taken into account.
</p>

<h2>Quantifiers by score</h2>
<p>
Which quantifier gave the highest quantification scores? If all
quantifiers where assigned the equal amount of praise and all
quantifiers on average gave similar quantification scores the boxes
would all be the same size.
</p>
<Suspense fallback={<LoadPlaceholder height={375} />}>
<QuantifiersByScore />
</Suspense>
<AnalyticsBox height={375} analysis={<QuantifiersByScore />} />
<p className="text-xs">
<b>*</b> The visualisation does not take into account that some
quantifiers get assigned less praise than others.
</p>

<h2>Quantifier scoring distribution</h2>
<p>
On average, does the quantifiers score praise similarly? Are some
quantifiers more generous than others?
</p>
<Suspense fallback={<LoadPlaceholder height={375} />}>
<QuantifierScoringDistribution />
</Suspense>
<AnalyticsBox height={375} analysis={<QuantifierScoringDistribution />} />

<h2>Quantification spread</h2>
<p>
When does the quantifiers agree or disagree? High quantification spread
means disagreement between quantifiers as to the importance of a
contribution.
</p>
<Suspense fallback={<LoadPlaceholder height={375} />}>
<QuantificationSpread />
</Suspense>
<AnalyticsBox height={375} analysis={<QuantificationSpread />} />
<p className="text-xs">
<b>*</b>The spread is measured by the difference between the highest and
lowest score given to a praise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
BoxAndWiskers,
BoxPlotDataPoint,
} from '@sgratzl/chartjs-chart-boxplot';
import { useEffect, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { Theme } from '@/model/theme';

// register controller in chart.js and ensure the defaults are set
ChartJS.register(BoxPlotController, BoxAndWiskers, LinearScale, CategoryScale);
Expand All @@ -21,9 +24,31 @@ export interface BoxPlotProps {
}

export const BoxPlot = ({ data, options }: BoxPlotProps): JSX.Element => {
const chartRef = useRef(null);
const theme = useRecoilValue(Theme);

useEffect(() => {
if (!chartRef.current) return;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const chart = chartRef.current as any;
const scales = chart.config.options.scales;
if (theme === 'Dark') {
scales.x.grid.color = 'rgb(168, 162, 158, 0.5)';
scales.x.ticks.color = 'rgb(255, 255, 255)';
scales.y.grid.color = 'rgb(168, 162, 158, 0.5)';
scales.y.ticks.color = 'rgb(168, 162, 158)';
} else {
scales.x.grid.color = 'rgb(168, 162, 158, 0.5)';
scales.x.ticks.color = 'rgb(41, 37, 36)';
scales.y.grid.color = 'rgb(168, 162, 158, 0.5)';
scales.y.ticks.color = 'rgb(168, 162, 158)';
}
chart.update();
}, [chartRef, theme]);

return (
<div>
<Chart type="boxplot" data={data} options={options} />
<Chart type="boxplot" data={data} options={options} ref={chartRef} />
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,15 @@ import {
SinglePeriod,
useLoadSinglePeriodDetails,
} from '@/model/periods';
import { ErrorPlaceholder } from '@/components/analytics/ErrorPlaceholder';
import { UserDataPoint, Treemap } from './Treemap';

export const GiversByNumber = (): JSX.Element => {
export const GiversByNumber = (): JSX.Element | null => {
const { periodId } = useParams<PeriodPageParams>();
useLoadSinglePeriodDetails(periodId);
const period = useRecoilValue(SinglePeriod(periodId));
const history = useHistory();

if (!period || !period.givers) {
return <ErrorPlaceholder height={375} />;
}
if (!period || !period.givers) return null;

const sortGiversByNumber = [...period.givers].sort(
(a, b) => b.praiseCount - a.praiseCount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ import {
SinglePeriod,
useLoadSinglePeriodDetails,
} from '@/model/periods';
import { ErrorPlaceholder } from '@/components/analytics/ErrorPlaceholder';
import { UserDataPoint, Treemap } from './Treemap';

export const GiversByScore = (): JSX.Element => {
export const GiversByScore = (): JSX.Element | null => {
const { periodId } = useParams<PeriodPageParams>();
useLoadSinglePeriodDetails(periodId);
const period = useRecoilValue(SinglePeriod(periodId));
const history = useHistory();

if (!period || !period.givers) {
return <ErrorPlaceholder height={375} />;
return null;
}

const sortGiversByScore = [...period.givers].sort(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import { useParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { PeriodPageParams } from '@/model/periods';
import { PeriodStatsSelector } from '@/model/periodAnalytics';
import { ErrorPlaceholder } from '@/components/analytics/ErrorPlaceholder';

export const PeriodStats = (): JSX.Element => {
export const PeriodStats = (): JSX.Element | null => {
const { periodId } = useParams<PeriodPageParams>();
const periodStats = useRecoilValue(PeriodStatsSelector(periodId));

if (!periodStats) {
return <ErrorPlaceholder height={100} />;
}
if (!periodStats) return null;

return (
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { BoxPlotDataPoint } from '@sgratzl/chartjs-chart-boxplot';
import { QuantificationDto } from 'api/dist/praise/types';
import { PeriodPageParams } from '@/model/periods';
import { PeriodPraiseOutliers } from '@/model/periodAnalytics';
import { ErrorPlaceholder } from '@/components/analytics/ErrorPlaceholder';
import { BoxPlot } from './Boxplot';
import { Pagination } from './Pagination';

Expand All @@ -22,9 +21,7 @@ export const QuantificationSpread = (): JSX.Element | null => {
const history = useHistory();
const page = useRecoilValue(QuantificationSpreadPagination);

if (!praiseDetailsStats) {
return <ErrorPlaceholder height={375} />;
}
if (!praiseDetailsStats) return null;

const sortedPraiseDetailsStats = [...praiseDetailsStats].sort(
(a, b) => b.scoreSpread - a.scoreSpread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { QuantificationDto } from 'api/dist/praise/types';
import { PeriodPageParams } from '@/model/periods';
import { AllPeriodQuantificationsGroupedByQuantifier } from '@/model/periodAnalytics';
import { ManyUsers } from '@/model/users';
import { ErrorPlaceholder } from '@/components/analytics/ErrorPlaceholder';
import { BoxPlot } from './Boxplot';

export const QuantifierScoringDistribution = (): JSX.Element | null => {
Expand All @@ -21,9 +20,7 @@ export const QuantifierScoringDistribution = (): JSX.Element | null => {
const userNames =
quantifierUsers && quantifierUsers.map((user) => user?.nameRealized);

if (!quantifierQuants || !userNames) {
return <ErrorPlaceholder height={375} />;
}
if (!quantifierQuants || !userNames) return null;

const options: ChartOptions<'boxplot'> = {
onClick: (event, elements): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import { useRecoilValue } from 'recoil';
import { ChartData, ChartOptions } from 'chart.js';
import { TreemapDataPoint } from 'chartjs-chart-treemap';
import { PeriodPageParams, SinglePeriod } from '@/model/periods';
import { ErrorPlaceholder } from '@/components/analytics/ErrorPlaceholder';
import { PeriodQuantifierStats } from '@/model/periodAnalytics';
import { ManyUsers } from '@/model/users';
import { UserDataPoint, Treemap } from './Treemap';

export const QuantifiersByScore = (): JSX.Element => {
export const QuantifiersByScore = (): JSX.Element | null => {
const { periodId } = useParams<PeriodPageParams>();
const history = useHistory();
const period = useRecoilValue(SinglePeriod(periodId));
Expand All @@ -17,9 +16,7 @@ export const QuantifiersByScore = (): JSX.Element => {
ManyUsers(allQuantifierStats && allQuantifierStats.map((q) => q._id))
);

if (!period || !period.receivers || !allQuantifierStats) {
return <ErrorPlaceholder height={375} />;
}
if (!period || !period.receivers || !allQuantifierStats) return null;

const sortStatsScore = [...allQuantifierStats].sort(
(a, b) => b.totalScore - a.totalScore
Expand Down
Loading

0 comments on commit 2e85d1e

Please sign in to comment.