Skip to content

Commit cc427b2

Browse files
committed
Commit progress on support for charts
1 parent ba43564 commit cc427b2

File tree

14 files changed

+324
-77
lines changed

14 files changed

+324
-77
lines changed

demo/src/payload.config.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ export default buildConfig({
1515
serverURL: "http://localhost:3000",
1616
admin: {
1717
user: Users.slug,
18+
// Used for development
19+
webpack: (config) => {
20+
const newConfig = {
21+
...config,
22+
resolve: {
23+
...config.resolve,
24+
alias: {
25+
...config.resolve.alias,
26+
react: path.join(__dirname, "../node_modules/react"),
27+
"react-dom": path.join(__dirname, "../node_modules/react-dom"),
28+
payload: path.join(__dirname, "../node_modules/payload"),
29+
},
30+
},
31+
};
32+
33+
return newConfig;
34+
},
1835
},
1936
collections: [Categories, Posts, Tags, Users, Media],
2037
typescript: {
@@ -34,6 +51,31 @@ export default buildConfig({
3451
collections: [
3552
{
3653
slug: Posts.slug,
54+
widgets: [
55+
{
56+
type: "chart",
57+
metric: "pageViews",
58+
timeframe: "7d",
59+
},
60+
{
61+
type: "chart",
62+
metric: "pageViews",
63+
timeframe: "30d",
64+
},
65+
{
66+
type: "chart",
67+
metric: "uniqueVisitors",
68+
timeframe: "month",
69+
},
70+
/* {
71+
type: "info",
72+
metric: "totalViews",
73+
},
74+
{
75+
type: "info",
76+
metric: "liveVisitors",
77+
}, */
78+
],
3779
},
3880
],
3981
}),

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@
2424
"license": "MIT",
2525
"peerDependencies": {
2626
"payload": "^1.6.16",
27-
"react": "^18.2.0"
27+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
2828
},
2929
"devDependencies": {
3030
"@types/react-router-dom": "^5.3.3",
3131
"payload": "^1.6.26",
3232
"react": "^18",
33-
"typescript": "^4.5.5"
33+
"typescript": "^5"
3434
},
3535
"files": [
3636
"dist",
Lines changed: 112 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,134 @@
1-
import React, { useEffect, useState } from "react";
2-
import type { DashboardAnalyticsConfig } from "../../types";
3-
import { AxisOptions, Chart } from "react-charts";
1+
import React, { useEffect, useState, lazy, useReducer, useRef } from "react";
2+
import type {
3+
DashboardAnalyticsConfig,
4+
ChartDataPoint,
5+
ChartWidget,
6+
} from "../../types";
7+
import type { AxisOptions } from "react-charts";
8+
import { MetricMap } from "../../providers/plausible/client";
9+
import { useTheme } from "payload/dist/admin/components/utilities/Theme";
410

5-
type Props = {};
11+
type ChartData = {
12+
label: string;
13+
data: ChartDataPoint[];
14+
};
15+
16+
type ChartOptions = {
17+
timeframe?: string;
18+
metric: ChartWidget["metric"];
19+
};
620

7-
type ChartDataType = {
8-
date: string;
9-
pageviews: number;
10-
visitors: number;
21+
type Props = {
22+
options: ChartOptions;
1123
};
1224

13-
export const ViewsChart: React.FC<Props> = (props) => {
14-
const [chartData, setChartData] = useState<ChartDataType[]>([]);
25+
const ChartComponent = lazy(() =>
26+
import("react-charts").then((module) => {
27+
return { default: module.Chart };
28+
})
29+
);
30+
31+
const ViewsChart: React.FC<Props> = ({ options }) => {
32+
const [chartData, setChartData] = useState<ChartData[]>([]);
33+
const chartRef = useRef<any>(null);
34+
const theme = useTheme();
1535

16-
/* useEffect(() => {
17-
const getChartData = fetch(`/api/analytics/globalChartData`).then(
18-
(response) => response.json()
19-
);
36+
const { timeframe, metric } = options;
37+
38+
const timeframeIndicator =
39+
timeframe === "month"
40+
? new Date().toLocaleString("default", { month: "long" })
41+
: timeframe ?? "30d";
42+
43+
useEffect(() => {
44+
const getChartData = fetch(`/api/analytics/globalChartData`, {
45+
method: "post",
46+
credentials: "include",
47+
headers: {
48+
Accept: "application/json",
49+
"Content-Type": "application/json",
50+
},
51+
body: JSON.stringify({ timeframe: timeframe, metric: metric }),
52+
}).then((response) => response.json());
2053

21-
getChartData.then((data) => {
22-
console.log("fetched data from backend:", data);
23-
setChartData(data);
54+
getChartData.then((data: ChartDataPoint[]) => {
55+
const processedData: ChartData[] = [
56+
{
57+
label: "Visitors",
58+
data: data,
59+
},
60+
];
61+
setChartData(processedData);
2462
});
25-
}, []); */
2663

27-
/* const primaryAxis = React.useMemo<AxisOptions<ChartDataType>>(
28-
() => ({
29-
getValue: (datum) => datum.date as unknown as Date,
30-
}),
31-
[]
32-
);
64+
const importChart = async () => {
65+
const { Chart } = await import("react-charts");
66+
chartRef.current = Chart;
67+
};
68+
69+
importChart();
70+
}, []);
71+
72+
const primaryAxis = React.useMemo<AxisOptions<ChartDataPoint>>(() => {
73+
return {
74+
getValue: (datum) => datum.timestamp,
75+
show: false,
76+
elementType: "line",
77+
showDatumElements: false,
78+
};
79+
}, []);
3380

34-
const secondaryAxes = React.useMemo<AxisOptions<ChartDataType>[]>(
81+
const secondaryAxes = React.useMemo<AxisOptions<ChartDataPoint>[]>(
3582
() => [
3683
{
37-
getValue: (datum) => datum.visitors,
84+
getValue: (datum) => {
85+
return datum.value;
86+
},
87+
elementType: "line",
3888
},
3989
],
4090
[]
41-
); */
91+
);
4292

4393
return (
44-
<div
94+
<section
4595
style={{
46-
marginBottom: "20px",
96+
marginBottom: "1.5rem",
4797
}}
4898
>
49-
my chart goes here:
50-
{/* <Chart
51-
options={{
52-
chartData,
53-
primaryAxis,
54-
secondaryAxes,
55-
}}
56-
/> */}
57-
</div>
99+
{chartRef?.current && chartData?.length && chartData.length > 0 ? (
100+
<>
101+
<h1 style={{ fontSize: "1.25rem", marginBottom: "0.5rem" }}>
102+
{MetricMap[metric].label} ({timeframeIndicator})
103+
</h1>
104+
<div style={{ minHeight: "200px", position: "relative" }}>
105+
<ChartComponent
106+
options={{
107+
data: chartData,
108+
dark: theme.theme === "dark",
109+
initialHeight: 220,
110+
tooltip: false,
111+
/* @ts-ignore */
112+
primaryAxis,
113+
/* @ts-ignore */
114+
secondaryAxes,
115+
}}
116+
/>
117+
</div>
118+
</>
119+
) : (
120+
<></>
121+
)}
122+
</section>
58123
);
59124
};
60125

61-
export const getViewsChart = (props: any) => <ViewsChart {...props} />;
126+
export const getViewsChart = (props?: any, options?: ChartOptions) => {
127+
const combinedProps: Props = {
128+
...props,
129+
options,
130+
};
131+
return <ViewsChart {...combinedProps} />;
132+
};
133+
134+
export default { ViewsChart, getViewsChart };

src/index.ts

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,48 @@
11
import type { Config as PayloadConfig } from "payload/config";
2-
import type { DashboardAnalyticsConfig } from "./types";
2+
import type {
3+
DashboardAnalyticsConfig,
4+
ChartWidget,
5+
InfoWidget,
6+
InnerWidget,
7+
} from "./types";
8+
import type { Field } from "payload/dist/fields/config/types";
39
import { extendWebpackConfig } from "./extendWebpackConfig";
410
import getProvider from "./providers";
511
import getGlobalAggregateData from "./routes/getGlobalAggregateData";
612
import getGlobalChartData from "./routes/getGlobalChartData";
713
import type { CollectionConfig } from "payload/dist/collections/config/types";
814
import { getViewsChart } from "./components/Charts/ViewsChart";
915

16+
const InnerWidgetMap: Record<InnerWidget["type"], (config: any) => Field> = {
17+
chart: (config: ChartWidget) => ({
18+
type: "ui",
19+
name: "dashboardAnalyticsViewsChart",
20+
admin: {
21+
position: "sidebar",
22+
components: {
23+
Field: (props: any) =>
24+
getViewsChart(props, {
25+
timeframe: config.timeframe,
26+
metric: config.metric,
27+
}),
28+
},
29+
},
30+
}),
31+
/* info: (config: InfoWidget) => ({
32+
type: "ui",
33+
name: "dashboardAnalyticsViewsChart",
34+
admin: {
35+
position: "sidebar",
36+
components: {
37+
Field: (props: any) =>
38+
getViewsChart(props, {
39+
metric: config.metric,
40+
}),
41+
},
42+
},
43+
}), */
44+
};
45+
1046
const payloadDashboardAnalytics =
1147
(incomingConfig: DashboardAnalyticsConfig) =>
1248
(config: PayloadConfig): PayloadConfig => {
@@ -19,6 +55,9 @@ const payloadDashboardAnalytics =
1955
...config,
2056
admin: {
2157
...admin,
58+
/* components: {
59+
beforeDashboard: [() => getViewsChart()],
60+
}, */
2261
webpack: extendWebpackConfig(config),
2362
},
2463
endpoints: [
@@ -38,16 +77,11 @@ const payloadDashboardAnalytics =
3877
...collection,
3978
fields: [
4079
...collection.fields,
41-
{
42-
type: "ui",
43-
name: "viewschart",
44-
admin: {
45-
position: "sidebar",
46-
components: {
47-
Field: (props) => getViewsChart(props),
48-
},
49-
},
50-
},
80+
...targetCollection.widgets.map((widget) => {
81+
const field = InnerWidgetMap[widget.type];
82+
83+
return field(widget);
84+
}),
5185
],
5286
};
5387

src/providers/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import plausible from "./plausible";
22
import type { Provider } from "../types";
3+
import { ChartWidget } from "../types";
4+
5+
type BaseOptions = {
6+
timeframe?: string;
7+
};
8+
9+
export interface GlobalAggregateOptions extends BaseOptions {}
10+
export interface GlobalChartOptions extends BaseOptions {
11+
metric: ChartWidget["metric"];
12+
}
313

414
export type ApiProvider = {
5-
getGlobalAggregateData: () => Promise<any>;
6-
getGlobalChartData: () => Promise<any>;
15+
getGlobalAggregateData: (options?: GlobalAggregateOptions) => Promise<any>;
16+
getGlobalChartData: (options?: GlobalChartOptions) => Promise<any>;
717
/* getPageAggregateData: () => {},
818
getPageChartData: () => {}, */
919
};

src/providers/plausible/client.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,45 @@
11
import type { PlausibleProvider } from "../../types";
2+
import { InnerWidget } from "../../types";
23

3-
function client(provider: PlausibleProvider, endpoint?: string) {
4+
type ClientOptions = {
5+
endpoint: string;
6+
timeframe?: string;
7+
metric?: InnerWidget["metric"];
8+
};
9+
10+
export const MetricMap: Record<
11+
InnerWidget["metric"],
12+
{ label: string; value: string }
13+
> = {
14+
pageViews: {
15+
label: "Page views",
16+
value: "pageviews",
17+
},
18+
uniqueVisitors: { label: "Visitors", value: "visitors" },
19+
};
20+
21+
function client(provider: PlausibleProvider, options: ClientOptions) {
22+
const { endpoint, timeframe, metric } = options;
423
const host = provider.host ?? `https://plausible.io`;
524
const apiVersion = `v1`; // for future use
625

26+
const period = timeframe ?? "30d";
27+
728
const url = new URL(`${host}/api/${apiVersion}${endpoint}`);
829
url.searchParams.append("site_id", provider.siteId);
930

10-
const plausibleMetrics = ["visitors", "pageviews"];
31+
const plausibleMetric = metric ? MetricMap[metric].value : "pageviews";
32+
33+
const plausibleMetrics = [plausibleMetric];
1134

1235
const baseUrl = String(url.href);
13-
url.searchParams.append("period", "30d");
36+
url.searchParams.append("period", period);
1437
url.searchParams.append("metrics", String(plausibleMetrics));
1538

1639
return {
1740
host: host,
1841
baseUrl: baseUrl,
42+
metric: plausibleMetric,
1943
url: url,
2044
fetch: async () =>
2145
await fetch(url, {

0 commit comments

Comments
 (0)