@thesquad-components/sqd-module-template 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/index.css +7606 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +194 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +190 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +3431 -0
- package/package.json +75 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type ActivityGaugeDatum = {
|
|
4
|
+
name: string;
|
|
5
|
+
value: number;
|
|
6
|
+
className?: string;
|
|
7
|
+
};
|
|
8
|
+
type ActivityGaugeProps = {
|
|
9
|
+
title?: string;
|
|
10
|
+
subtitle?: string;
|
|
11
|
+
data?: ActivityGaugeDatum[];
|
|
12
|
+
maxValue?: number;
|
|
13
|
+
};
|
|
14
|
+
declare const ActivityGaugeLg: ({ title, subtitle, data, maxValue, }: ActivityGaugeProps) => react_jsx_runtime.JSX.Element;
|
|
15
|
+
|
|
16
|
+
type TasksActivityGaugeProps = {
|
|
17
|
+
daysBack?: number;
|
|
18
|
+
};
|
|
19
|
+
declare const TasksActivityGauge: ({ daysBack }: TasksActivityGaugeProps) => react_jsx_runtime.JSX.Element;
|
|
20
|
+
|
|
21
|
+
type TasksCountResponse = {
|
|
22
|
+
count: number;
|
|
23
|
+
daysBack: number;
|
|
24
|
+
since: string;
|
|
25
|
+
};
|
|
26
|
+
type GetTasksCountOptions = {
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
moduleKey?: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
};
|
|
31
|
+
declare const getTasksCount: (daysBack: number, options?: GetTasksCountOptions) => Promise<number>;
|
|
32
|
+
|
|
33
|
+
export { type ActivityGaugeDatum, ActivityGaugeLg, type ActivityGaugeProps, TasksActivityGauge, type TasksActivityGaugeProps, type TasksCountResponse, getTasksCount };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type ActivityGaugeDatum = {
|
|
4
|
+
name: string;
|
|
5
|
+
value: number;
|
|
6
|
+
className?: string;
|
|
7
|
+
};
|
|
8
|
+
type ActivityGaugeProps = {
|
|
9
|
+
title?: string;
|
|
10
|
+
subtitle?: string;
|
|
11
|
+
data?: ActivityGaugeDatum[];
|
|
12
|
+
maxValue?: number;
|
|
13
|
+
};
|
|
14
|
+
declare const ActivityGaugeLg: ({ title, subtitle, data, maxValue, }: ActivityGaugeProps) => react_jsx_runtime.JSX.Element;
|
|
15
|
+
|
|
16
|
+
type TasksActivityGaugeProps = {
|
|
17
|
+
daysBack?: number;
|
|
18
|
+
};
|
|
19
|
+
declare const TasksActivityGauge: ({ daysBack }: TasksActivityGaugeProps) => react_jsx_runtime.JSX.Element;
|
|
20
|
+
|
|
21
|
+
type TasksCountResponse = {
|
|
22
|
+
count: number;
|
|
23
|
+
daysBack: number;
|
|
24
|
+
since: string;
|
|
25
|
+
};
|
|
26
|
+
type GetTasksCountOptions = {
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
moduleKey?: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
};
|
|
31
|
+
declare const getTasksCount: (daysBack: number, options?: GetTasksCountOptions) => Promise<number>;
|
|
32
|
+
|
|
33
|
+
export { type ActivityGaugeDatum, ActivityGaugeLg, type ActivityGaugeProps, TasksActivityGauge, type TasksActivityGaugeProps, type TasksCountResponse, getTasksCount };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var recharts = require('recharts');
|
|
4
|
+
var tailwindMerge = require('tailwind-merge');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
|
|
8
|
+
var twMerge = tailwindMerge.extendTailwindMerge({
|
|
9
|
+
extend: {
|
|
10
|
+
theme: {
|
|
11
|
+
text: ["display-xs", "display-sm", "display-md", "display-lg", "display-xl", "display-2xl"]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
var cx = twMerge;
|
|
16
|
+
var ChartLegendContent = ({ payload = [] }) => {
|
|
17
|
+
if (payload.length === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center justify-center gap-x-4 gap-y-2", children: payload.map((entry, index) => {
|
|
21
|
+
var _a, _b;
|
|
22
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
23
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
24
|
+
"span",
|
|
25
|
+
{
|
|
26
|
+
className: cx(
|
|
27
|
+
"size-2 rounded-full bg-utility-gray-300",
|
|
28
|
+
((_b = entry.payload) == null ? void 0 : _b.className) ? entry.payload.className.replace("text-", "bg-") : void 0
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
),
|
|
32
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-secondary", children: entry.value })
|
|
33
|
+
] }, `${(_a = entry.value) != null ? _a : "item"}-${index}`);
|
|
34
|
+
}) });
|
|
35
|
+
};
|
|
36
|
+
var ChartTooltipContent = ({ active, payload, label, isRadialChart }) => {
|
|
37
|
+
if (!active || !payload || payload.length === 0) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-secondary bg-primary px-3 py-2 shadow-sm", children: [
|
|
41
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-medium text-tertiary", children: label }),
|
|
42
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 space-y-1", children: payload.map((entry, index) => {
|
|
43
|
+
var _a, _b, _c;
|
|
44
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
45
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
46
|
+
"span",
|
|
47
|
+
{
|
|
48
|
+
className: cx(
|
|
49
|
+
"size-2 rounded-full bg-utility-gray-300",
|
|
50
|
+
((_b = entry.payload) == null ? void 0 : _b.className) ? entry.payload.className.replace("text-", "bg-") : void 0
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
),
|
|
54
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-secondary", children: (_c = entry.name) != null ? _c : isRadialChart ? "Value" : label }),
|
|
55
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto font-medium text-primary", children: entry.value })
|
|
56
|
+
] }, `${(_a = entry.name) != null ? _a : "value"}-${index}`);
|
|
57
|
+
}) })
|
|
58
|
+
] });
|
|
59
|
+
};
|
|
60
|
+
var radialData = [
|
|
61
|
+
{
|
|
62
|
+
name: "Series 3",
|
|
63
|
+
value: 660,
|
|
64
|
+
className: "text-utility-brand-400"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "Series 2",
|
|
68
|
+
value: 774,
|
|
69
|
+
className: "text-utility-brand-600"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "Series 1",
|
|
73
|
+
value: 866,
|
|
74
|
+
className: "text-utility-brand-700"
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
var ActivityGaugeLg = ({
|
|
78
|
+
title = "1,000",
|
|
79
|
+
subtitle = "Active users",
|
|
80
|
+
data = radialData,
|
|
81
|
+
maxValue = 1e3
|
|
82
|
+
}) => {
|
|
83
|
+
return /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { height: 356, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
84
|
+
recharts.RadialBarChart,
|
|
85
|
+
{
|
|
86
|
+
data,
|
|
87
|
+
accessibilityLayer: true,
|
|
88
|
+
innerRadius: 84,
|
|
89
|
+
outerRadius: 154,
|
|
90
|
+
startAngle: 90,
|
|
91
|
+
endAngle: 360 + 90,
|
|
92
|
+
className: "font-medium text-tertiary [&_.recharts-polar-grid]:text-utility-gray-100 [&_.recharts-text]:text-sm",
|
|
93
|
+
margin: {
|
|
94
|
+
left: 0,
|
|
95
|
+
right: 0,
|
|
96
|
+
top: 0,
|
|
97
|
+
bottom: 0
|
|
98
|
+
},
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.PolarAngleAxis, { tick: false, domain: [0, maxValue], type: "number", reversed: true }),
|
|
101
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { verticalAlign: "bottom", align: "center", layout: "horizontal", content: /* @__PURE__ */ jsxRuntime.jsx(ChartLegendContent, {}) }),
|
|
102
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(ChartTooltipContent, { isRadialChart: true }) }),
|
|
103
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
104
|
+
recharts.RadialBar,
|
|
105
|
+
{
|
|
106
|
+
isAnimationActive: false,
|
|
107
|
+
dataKey: "value",
|
|
108
|
+
cornerRadius: 99,
|
|
109
|
+
fill: "currentColor",
|
|
110
|
+
background: {
|
|
111
|
+
className: "fill-utility-gray-100"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
(title || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs("text", { x: "50%", y: "50%", textAnchor: "middle", dominantBaseline: "middle", children: [
|
|
116
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsx(
|
|
117
|
+
"tspan",
|
|
118
|
+
{
|
|
119
|
+
x: "50%",
|
|
120
|
+
dy: title ? "-1.4em" : "1%",
|
|
121
|
+
className: cx("fill-current text-tertiary", "text-sm font-medium"),
|
|
122
|
+
children: subtitle
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
title && /* @__PURE__ */ jsxRuntime.jsx(
|
|
126
|
+
"tspan",
|
|
127
|
+
{
|
|
128
|
+
x: "50%",
|
|
129
|
+
dy: subtitle ? "1em" : "1%",
|
|
130
|
+
className: cx("fill-current text-primary", "text-display-md font-semibold"),
|
|
131
|
+
children: title
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
] })
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
) });
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/api/tasks.ts
|
|
141
|
+
var DEFAULT_API_BASE_URL = "https://api.thesqd.com";
|
|
142
|
+
var DEFAULT_MODULE_KEY = "e166f127e73167b6646eeaeff8837566d3c27d4002fb122bc49d4fb06d97d67e";
|
|
143
|
+
var getTasksCount = async (daysBack, options = {}) => {
|
|
144
|
+
var _a, _b, _c, _d, _e;
|
|
145
|
+
const resolvedDaysBack = Number.isFinite(daysBack) && daysBack > 0 ? Math.floor(daysBack) : 7;
|
|
146
|
+
const baseUrl = (_b = (_a = options.baseUrl) != null ? _a : process.env.NEXT_PUBLIC_SQD_API_BASE_URL) != null ? _b : DEFAULT_API_BASE_URL;
|
|
147
|
+
const moduleKey = (_d = (_c = options.moduleKey) != null ? _c : process.env.NEXT_PUBLIC_SQD_MODULE_KEY) != null ? _d : DEFAULT_MODULE_KEY;
|
|
148
|
+
const url = new URL("/api/tasks/count", baseUrl);
|
|
149
|
+
url.searchParams.set("daysBack", String(resolvedDaysBack));
|
|
150
|
+
const response = await fetch(url.toString(), {
|
|
151
|
+
method: "GET",
|
|
152
|
+
headers: {
|
|
153
|
+
"Content-Type": "application/json",
|
|
154
|
+
"x-sqd-module-key": moduleKey
|
|
155
|
+
},
|
|
156
|
+
cache: "no-store",
|
|
157
|
+
signal: options.signal
|
|
158
|
+
});
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw new Error(`Failed to fetch tasks count (${response.status})`);
|
|
161
|
+
}
|
|
162
|
+
const payload = await response.json();
|
|
163
|
+
return (_e = payload.count) != null ? _e : 0;
|
|
164
|
+
};
|
|
165
|
+
var TasksActivityGauge = ({ daysBack = 7 }) => {
|
|
166
|
+
const [count, setCount] = react.useState(null);
|
|
167
|
+
const [error, setError] = react.useState(null);
|
|
168
|
+
const subtitle = react.useMemo(() => `Tasks last ${daysBack} days`, [daysBack]);
|
|
169
|
+
react.useEffect(() => {
|
|
170
|
+
let isActive = true;
|
|
171
|
+
setError(null);
|
|
172
|
+
setCount(null);
|
|
173
|
+
getTasksCount(daysBack).then((total) => {
|
|
174
|
+
if (isActive) {
|
|
175
|
+
setCount(total);
|
|
176
|
+
}
|
|
177
|
+
}).catch((err) => {
|
|
178
|
+
if (isActive) {
|
|
179
|
+
setError(err instanceof Error ? err.message : "Failed to load tasks count");
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return () => {
|
|
183
|
+
isActive = false;
|
|
184
|
+
};
|
|
185
|
+
}, [daysBack]);
|
|
186
|
+
const title = error ? "\u2014" : count === null ? "\u2026" : count.toLocaleString();
|
|
187
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ActivityGaugeLg, { title, subtitle });
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
exports.ActivityGaugeLg = ActivityGaugeLg;
|
|
191
|
+
exports.TasksActivityGauge = TasksActivityGauge;
|
|
192
|
+
exports.getTasksCount = getTasksCount;
|
|
193
|
+
//# sourceMappingURL=index.js.map
|
|
194
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/cx.ts","../src/components/application/charts/charts-base.tsx","../src/components/application/charts/activity-gauge-lg.tsx","../src/api/tasks.ts","../src/components/application/charts/tasks-activity-gauge.tsx"],"names":["extendTailwindMerge","jsx","jsxs","ResponsiveContainer","RadialBarChart","PolarAngleAxis","Legend","Tooltip","RadialBar","useState","useMemo","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import { extendTailwindMerge } from \"tailwind-merge\";\n\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: {\n text: [\"display-xs\", \"display-sm\", \"display-md\", \"display-lg\", \"display-xl\", \"display-2xl\"],\n },\n },\n});\n\n/**\n * This function is a wrapper around the twMerge function.\n * It is used to merge the classes inside style objects.\n */\nexport const cx = twMerge;\n\n/**\n * This function does nothing besides helping us to be able to\n * sort the classes inside style objects which is not supported\n * by the Tailwind IntelliSense by default.\n */\nexport function sortCx<T extends Record<string, string | number | Record<string, string | number | Record<string, string | number>>>>(classes: T): T {\n return classes;\n}\n","\"use client\";\n\nimport type { TooltipProps } from \"recharts\";\n\nimport { cx } from \"@/utils/cx\";\n\ntype LegendPayload = {\n value?: string;\n payload?: {\n className?: string;\n };\n};\n\ntype LegendProps = {\n payload?: LegendPayload[];\n};\n\nexport const ChartLegendContent = ({ payload = [] }: LegendProps) => {\n if (payload.length === 0) {\n return null;\n }\n\n return (\n <div className=\"flex flex-wrap items-center justify-center gap-x-4 gap-y-2\">\n {payload.map((entry, index) => (\n <div key={`${entry.value ?? \"item\"}-${index}`} className=\"flex items-center gap-2\">\n <span\n className={cx(\n \"size-2 rounded-full bg-utility-gray-300\",\n entry.payload?.className ? entry.payload.className.replace(\"text-\", \"bg-\") : undefined,\n )}\n />\n <span className=\"text-xs font-medium text-secondary\">{entry.value}</span>\n </div>\n ))}\n </div>\n );\n};\n\ntype ChartTooltipContentProps = TooltipProps<number, string> & {\n isRadialChart?: boolean;\n};\n\nexport const ChartTooltipContent = ({ active, payload, label, isRadialChart }: ChartTooltipContentProps) => {\n if (!active || !payload || payload.length === 0) {\n return null;\n }\n\n return (\n <div className=\"rounded-lg border border-secondary bg-primary px-3 py-2 shadow-sm\">\n {label && <div className=\"text-xs font-medium text-tertiary\">{label}</div>}\n <div className=\"mt-1 space-y-1\">\n {payload.map((entry, index) => (\n <div key={`${entry.name ?? \"value\"}-${index}`} className=\"flex items-center gap-2 text-xs\">\n <span\n className={cx(\n \"size-2 rounded-full bg-utility-gray-300\",\n entry.payload?.className ? entry.payload.className.replace(\"text-\", \"bg-\") : undefined,\n )}\n />\n <span className=\"text-secondary\">{entry.name ?? (isRadialChart ? \"Value\" : label)}</span>\n <span className=\"ml-auto font-medium text-primary\">{entry.value}</span>\n </div>\n ))}\n </div>\n </div>\n );\n};\n","\"use client\";\n\nimport { Legend, PolarAngleAxis, RadialBar, RadialBarChart, ResponsiveContainer, Tooltip } from \"recharts\";\n\nimport { ChartLegendContent, ChartTooltipContent } from \"@/components/application/charts/charts-base\";\nimport { cx } from \"@/utils/cx\";\n\nexport type ActivityGaugeDatum = {\n name: string;\n value: number;\n className?: string;\n};\n\nexport type ActivityGaugeProps = {\n title?: string;\n subtitle?: string;\n data?: ActivityGaugeDatum[];\n maxValue?: number;\n};\n\nconst radialData: ActivityGaugeDatum[] = [\n {\n name: \"Series 3\",\n value: 660,\n className: \"text-utility-brand-400\",\n },\n {\n name: \"Series 2\",\n value: 774,\n className: \"text-utility-brand-600\",\n },\n {\n name: \"Series 1\",\n value: 866,\n className: \"text-utility-brand-700\",\n },\n];\n\nexport const ActivityGaugeLg = ({\n title = \"1,000\",\n subtitle = \"Active users\",\n data = radialData,\n maxValue = 1000,\n}: ActivityGaugeProps) => {\n return (\n <ResponsiveContainer height={356}>\n <RadialBarChart\n data={data}\n accessibilityLayer\n innerRadius={84}\n outerRadius={154}\n startAngle={90}\n endAngle={360 + 90}\n className=\"font-medium text-tertiary [&_.recharts-polar-grid]:text-utility-gray-100 [&_.recharts-text]:text-sm\"\n margin={{\n left: 0,\n right: 0,\n top: 0,\n bottom: 0,\n }}\n >\n <PolarAngleAxis tick={false} domain={[0, maxValue]} type=\"number\" reversed />\n\n <Legend verticalAlign=\"bottom\" align=\"center\" layout=\"horizontal\" content={<ChartLegendContent />} />\n\n <Tooltip content={<ChartTooltipContent isRadialChart />} />\n\n <RadialBar\n isAnimationActive={false}\n dataKey=\"value\"\n cornerRadius={99}\n fill=\"currentColor\"\n background={{\n className: \"fill-utility-gray-100\",\n }}\n />\n\n {(title || subtitle) && (\n <text x=\"50%\" y=\"50%\" textAnchor=\"middle\" dominantBaseline=\"middle\">\n {subtitle && (\n <tspan\n x=\"50%\"\n dy={title ? \"-1.4em\" : \"1%\"}\n className={cx(\"fill-current text-tertiary\", \"text-sm font-medium\")}\n >\n {subtitle}\n </tspan>\n )}\n {title && (\n <tspan\n x=\"50%\"\n dy={subtitle ? \"1em\" : \"1%\"}\n className={cx(\"fill-current text-primary\", \"text-display-md font-semibold\")}\n >\n {title}\n </tspan>\n )}\n </text>\n )}\n </RadialBarChart>\n </ResponsiveContainer>\n );\n};\n","export type TasksCountResponse = {\n count: number;\n daysBack: number;\n since: string;\n};\n\ntype GetTasksCountOptions = {\n baseUrl?: string;\n moduleKey?: string;\n signal?: AbortSignal;\n};\n\nconst DEFAULT_API_BASE_URL = \"https://api.thesqd.com\";\nconst DEFAULT_MODULE_KEY = \"e166f127e73167b6646eeaeff8837566d3c27d4002fb122bc49d4fb06d97d67e\";\n\nexport const getTasksCount = async (daysBack: number, options: GetTasksCountOptions = {}) => {\n const resolvedDaysBack = Number.isFinite(daysBack) && daysBack > 0 ? Math.floor(daysBack) : 7;\n const baseUrl = options.baseUrl ?? process.env.NEXT_PUBLIC_SQD_API_BASE_URL ?? DEFAULT_API_BASE_URL;\n const moduleKey = options.moduleKey ?? process.env.NEXT_PUBLIC_SQD_MODULE_KEY ?? DEFAULT_MODULE_KEY;\n const url = new URL(\"/api/tasks/count\", baseUrl);\n\n url.searchParams.set(\"daysBack\", String(resolvedDaysBack));\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-sqd-module-key\": moduleKey,\n },\n cache: \"no-store\",\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch tasks count (${response.status})`);\n }\n\n const payload = (await response.json()) as TasksCountResponse;\n\n return payload.count ?? 0;\n};\n","\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport { getTasksCount } from \"@/api/tasks\";\nimport { ActivityGaugeLg } from \"@/components/application/charts/activity-gauge-lg\";\n\nexport type TasksActivityGaugeProps = {\n daysBack?: number;\n};\n\nexport const TasksActivityGauge = ({ daysBack = 7 }: TasksActivityGaugeProps) => {\n const [count, setCount] = useState<number | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const subtitle = useMemo(() => `Tasks last ${daysBack} days`, [daysBack]);\n\n useEffect(() => {\n let isActive = true;\n\n setError(null);\n setCount(null);\n\n getTasksCount(daysBack)\n .then((total) => {\n if (isActive) {\n setCount(total);\n }\n })\n .catch((err: unknown) => {\n if (isActive) {\n setError(err instanceof Error ? err.message : \"Failed to load tasks count\");\n }\n });\n\n return () => {\n isActive = false;\n };\n }, [daysBack]);\n\n const title = error ? \"—\" : count === null ? \"…\" : count.toLocaleString();\n\n return <ActivityGaugeLg title={title} subtitle={subtitle} />;\n};\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { ResponsiveContainer, RadialBarChart, PolarAngleAxis, Legend, Tooltip, RadialBar } from 'recharts';
|
|
2
|
+
import { extendTailwindMerge } from 'tailwind-merge';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import { useState, useMemo, useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
var twMerge = extendTailwindMerge({
|
|
7
|
+
extend: {
|
|
8
|
+
theme: {
|
|
9
|
+
text: ["display-xs", "display-sm", "display-md", "display-lg", "display-xl", "display-2xl"]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
var cx = twMerge;
|
|
14
|
+
var ChartLegendContent = ({ payload = [] }) => {
|
|
15
|
+
if (payload.length === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-center gap-x-4 gap-y-2", children: payload.map((entry, index) => {
|
|
19
|
+
var _a, _b;
|
|
20
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
21
|
+
/* @__PURE__ */ jsx(
|
|
22
|
+
"span",
|
|
23
|
+
{
|
|
24
|
+
className: cx(
|
|
25
|
+
"size-2 rounded-full bg-utility-gray-300",
|
|
26
|
+
((_b = entry.payload) == null ? void 0 : _b.className) ? entry.payload.className.replace("text-", "bg-") : void 0
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
),
|
|
30
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-secondary", children: entry.value })
|
|
31
|
+
] }, `${(_a = entry.value) != null ? _a : "item"}-${index}`);
|
|
32
|
+
}) });
|
|
33
|
+
};
|
|
34
|
+
var ChartTooltipContent = ({ active, payload, label, isRadialChart }) => {
|
|
35
|
+
if (!active || !payload || payload.length === 0) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-secondary bg-primary px-3 py-2 shadow-sm", children: [
|
|
39
|
+
label && /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-tertiary", children: label }),
|
|
40
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 space-y-1", children: payload.map((entry, index) => {
|
|
41
|
+
var _a, _b, _c;
|
|
42
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
"span",
|
|
45
|
+
{
|
|
46
|
+
className: cx(
|
|
47
|
+
"size-2 rounded-full bg-utility-gray-300",
|
|
48
|
+
((_b = entry.payload) == null ? void 0 : _b.className) ? entry.payload.className.replace("text-", "bg-") : void 0
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
),
|
|
52
|
+
/* @__PURE__ */ jsx("span", { className: "text-secondary", children: (_c = entry.name) != null ? _c : isRadialChart ? "Value" : label }),
|
|
53
|
+
/* @__PURE__ */ jsx("span", { className: "ml-auto font-medium text-primary", children: entry.value })
|
|
54
|
+
] }, `${(_a = entry.name) != null ? _a : "value"}-${index}`);
|
|
55
|
+
}) })
|
|
56
|
+
] });
|
|
57
|
+
};
|
|
58
|
+
var radialData = [
|
|
59
|
+
{
|
|
60
|
+
name: "Series 3",
|
|
61
|
+
value: 660,
|
|
62
|
+
className: "text-utility-brand-400"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "Series 2",
|
|
66
|
+
value: 774,
|
|
67
|
+
className: "text-utility-brand-600"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "Series 1",
|
|
71
|
+
value: 866,
|
|
72
|
+
className: "text-utility-brand-700"
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
var ActivityGaugeLg = ({
|
|
76
|
+
title = "1,000",
|
|
77
|
+
subtitle = "Active users",
|
|
78
|
+
data = radialData,
|
|
79
|
+
maxValue = 1e3
|
|
80
|
+
}) => {
|
|
81
|
+
return /* @__PURE__ */ jsx(ResponsiveContainer, { height: 356, children: /* @__PURE__ */ jsxs(
|
|
82
|
+
RadialBarChart,
|
|
83
|
+
{
|
|
84
|
+
data,
|
|
85
|
+
accessibilityLayer: true,
|
|
86
|
+
innerRadius: 84,
|
|
87
|
+
outerRadius: 154,
|
|
88
|
+
startAngle: 90,
|
|
89
|
+
endAngle: 360 + 90,
|
|
90
|
+
className: "font-medium text-tertiary [&_.recharts-polar-grid]:text-utility-gray-100 [&_.recharts-text]:text-sm",
|
|
91
|
+
margin: {
|
|
92
|
+
left: 0,
|
|
93
|
+
right: 0,
|
|
94
|
+
top: 0,
|
|
95
|
+
bottom: 0
|
|
96
|
+
},
|
|
97
|
+
children: [
|
|
98
|
+
/* @__PURE__ */ jsx(PolarAngleAxis, { tick: false, domain: [0, maxValue], type: "number", reversed: true }),
|
|
99
|
+
/* @__PURE__ */ jsx(Legend, { verticalAlign: "bottom", align: "center", layout: "horizontal", content: /* @__PURE__ */ jsx(ChartLegendContent, {}) }),
|
|
100
|
+
/* @__PURE__ */ jsx(Tooltip, { content: /* @__PURE__ */ jsx(ChartTooltipContent, { isRadialChart: true }) }),
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
RadialBar,
|
|
103
|
+
{
|
|
104
|
+
isAnimationActive: false,
|
|
105
|
+
dataKey: "value",
|
|
106
|
+
cornerRadius: 99,
|
|
107
|
+
fill: "currentColor",
|
|
108
|
+
background: {
|
|
109
|
+
className: "fill-utility-gray-100"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
(title || subtitle) && /* @__PURE__ */ jsxs("text", { x: "50%", y: "50%", textAnchor: "middle", dominantBaseline: "middle", children: [
|
|
114
|
+
subtitle && /* @__PURE__ */ jsx(
|
|
115
|
+
"tspan",
|
|
116
|
+
{
|
|
117
|
+
x: "50%",
|
|
118
|
+
dy: title ? "-1.4em" : "1%",
|
|
119
|
+
className: cx("fill-current text-tertiary", "text-sm font-medium"),
|
|
120
|
+
children: subtitle
|
|
121
|
+
}
|
|
122
|
+
),
|
|
123
|
+
title && /* @__PURE__ */ jsx(
|
|
124
|
+
"tspan",
|
|
125
|
+
{
|
|
126
|
+
x: "50%",
|
|
127
|
+
dy: subtitle ? "1em" : "1%",
|
|
128
|
+
className: cx("fill-current text-primary", "text-display-md font-semibold"),
|
|
129
|
+
children: title
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
] })
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
) });
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/api/tasks.ts
|
|
139
|
+
var DEFAULT_API_BASE_URL = "https://api.thesqd.com";
|
|
140
|
+
var DEFAULT_MODULE_KEY = "e166f127e73167b6646eeaeff8837566d3c27d4002fb122bc49d4fb06d97d67e";
|
|
141
|
+
var getTasksCount = async (daysBack, options = {}) => {
|
|
142
|
+
var _a, _b, _c, _d, _e;
|
|
143
|
+
const resolvedDaysBack = Number.isFinite(daysBack) && daysBack > 0 ? Math.floor(daysBack) : 7;
|
|
144
|
+
const baseUrl = (_b = (_a = options.baseUrl) != null ? _a : process.env.NEXT_PUBLIC_SQD_API_BASE_URL) != null ? _b : DEFAULT_API_BASE_URL;
|
|
145
|
+
const moduleKey = (_d = (_c = options.moduleKey) != null ? _c : process.env.NEXT_PUBLIC_SQD_MODULE_KEY) != null ? _d : DEFAULT_MODULE_KEY;
|
|
146
|
+
const url = new URL("/api/tasks/count", baseUrl);
|
|
147
|
+
url.searchParams.set("daysBack", String(resolvedDaysBack));
|
|
148
|
+
const response = await fetch(url.toString(), {
|
|
149
|
+
method: "GET",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
"x-sqd-module-key": moduleKey
|
|
153
|
+
},
|
|
154
|
+
cache: "no-store",
|
|
155
|
+
signal: options.signal
|
|
156
|
+
});
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
throw new Error(`Failed to fetch tasks count (${response.status})`);
|
|
159
|
+
}
|
|
160
|
+
const payload = await response.json();
|
|
161
|
+
return (_e = payload.count) != null ? _e : 0;
|
|
162
|
+
};
|
|
163
|
+
var TasksActivityGauge = ({ daysBack = 7 }) => {
|
|
164
|
+
const [count, setCount] = useState(null);
|
|
165
|
+
const [error, setError] = useState(null);
|
|
166
|
+
const subtitle = useMemo(() => `Tasks last ${daysBack} days`, [daysBack]);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
let isActive = true;
|
|
169
|
+
setError(null);
|
|
170
|
+
setCount(null);
|
|
171
|
+
getTasksCount(daysBack).then((total) => {
|
|
172
|
+
if (isActive) {
|
|
173
|
+
setCount(total);
|
|
174
|
+
}
|
|
175
|
+
}).catch((err) => {
|
|
176
|
+
if (isActive) {
|
|
177
|
+
setError(err instanceof Error ? err.message : "Failed to load tasks count");
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return () => {
|
|
181
|
+
isActive = false;
|
|
182
|
+
};
|
|
183
|
+
}, [daysBack]);
|
|
184
|
+
const title = error ? "\u2014" : count === null ? "\u2026" : count.toLocaleString();
|
|
185
|
+
return /* @__PURE__ */ jsx(ActivityGaugeLg, { title, subtitle });
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export { ActivityGaugeLg, TasksActivityGauge, getTasksCount };
|
|
189
|
+
//# sourceMappingURL=index.mjs.map
|
|
190
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/cx.ts","../src/components/application/charts/charts-base.tsx","../src/components/application/charts/activity-gauge-lg.tsx","../src/api/tasks.ts","../src/components/application/charts/tasks-activity-gauge.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"index.mjs","sourcesContent":["import { extendTailwindMerge } from \"tailwind-merge\";\n\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: {\n text: [\"display-xs\", \"display-sm\", \"display-md\", \"display-lg\", \"display-xl\", \"display-2xl\"],\n },\n },\n});\n\n/**\n * This function is a wrapper around the twMerge function.\n * It is used to merge the classes inside style objects.\n */\nexport const cx = twMerge;\n\n/**\n * This function does nothing besides helping us to be able to\n * sort the classes inside style objects which is not supported\n * by the Tailwind IntelliSense by default.\n */\nexport function sortCx<T extends Record<string, string | number | Record<string, string | number | Record<string, string | number>>>>(classes: T): T {\n return classes;\n}\n","\"use client\";\n\nimport type { TooltipProps } from \"recharts\";\n\nimport { cx } from \"@/utils/cx\";\n\ntype LegendPayload = {\n value?: string;\n payload?: {\n className?: string;\n };\n};\n\ntype LegendProps = {\n payload?: LegendPayload[];\n};\n\nexport const ChartLegendContent = ({ payload = [] }: LegendProps) => {\n if (payload.length === 0) {\n return null;\n }\n\n return (\n <div className=\"flex flex-wrap items-center justify-center gap-x-4 gap-y-2\">\n {payload.map((entry, index) => (\n <div key={`${entry.value ?? \"item\"}-${index}`} className=\"flex items-center gap-2\">\n <span\n className={cx(\n \"size-2 rounded-full bg-utility-gray-300\",\n entry.payload?.className ? entry.payload.className.replace(\"text-\", \"bg-\") : undefined,\n )}\n />\n <span className=\"text-xs font-medium text-secondary\">{entry.value}</span>\n </div>\n ))}\n </div>\n );\n};\n\ntype ChartTooltipContentProps = TooltipProps<number, string> & {\n isRadialChart?: boolean;\n};\n\nexport const ChartTooltipContent = ({ active, payload, label, isRadialChart }: ChartTooltipContentProps) => {\n if (!active || !payload || payload.length === 0) {\n return null;\n }\n\n return (\n <div className=\"rounded-lg border border-secondary bg-primary px-3 py-2 shadow-sm\">\n {label && <div className=\"text-xs font-medium text-tertiary\">{label}</div>}\n <div className=\"mt-1 space-y-1\">\n {payload.map((entry, index) => (\n <div key={`${entry.name ?? \"value\"}-${index}`} className=\"flex items-center gap-2 text-xs\">\n <span\n className={cx(\n \"size-2 rounded-full bg-utility-gray-300\",\n entry.payload?.className ? entry.payload.className.replace(\"text-\", \"bg-\") : undefined,\n )}\n />\n <span className=\"text-secondary\">{entry.name ?? (isRadialChart ? \"Value\" : label)}</span>\n <span className=\"ml-auto font-medium text-primary\">{entry.value}</span>\n </div>\n ))}\n </div>\n </div>\n );\n};\n","\"use client\";\n\nimport { Legend, PolarAngleAxis, RadialBar, RadialBarChart, ResponsiveContainer, Tooltip } from \"recharts\";\n\nimport { ChartLegendContent, ChartTooltipContent } from \"@/components/application/charts/charts-base\";\nimport { cx } from \"@/utils/cx\";\n\nexport type ActivityGaugeDatum = {\n name: string;\n value: number;\n className?: string;\n};\n\nexport type ActivityGaugeProps = {\n title?: string;\n subtitle?: string;\n data?: ActivityGaugeDatum[];\n maxValue?: number;\n};\n\nconst radialData: ActivityGaugeDatum[] = [\n {\n name: \"Series 3\",\n value: 660,\n className: \"text-utility-brand-400\",\n },\n {\n name: \"Series 2\",\n value: 774,\n className: \"text-utility-brand-600\",\n },\n {\n name: \"Series 1\",\n value: 866,\n className: \"text-utility-brand-700\",\n },\n];\n\nexport const ActivityGaugeLg = ({\n title = \"1,000\",\n subtitle = \"Active users\",\n data = radialData,\n maxValue = 1000,\n}: ActivityGaugeProps) => {\n return (\n <ResponsiveContainer height={356}>\n <RadialBarChart\n data={data}\n accessibilityLayer\n innerRadius={84}\n outerRadius={154}\n startAngle={90}\n endAngle={360 + 90}\n className=\"font-medium text-tertiary [&_.recharts-polar-grid]:text-utility-gray-100 [&_.recharts-text]:text-sm\"\n margin={{\n left: 0,\n right: 0,\n top: 0,\n bottom: 0,\n }}\n >\n <PolarAngleAxis tick={false} domain={[0, maxValue]} type=\"number\" reversed />\n\n <Legend verticalAlign=\"bottom\" align=\"center\" layout=\"horizontal\" content={<ChartLegendContent />} />\n\n <Tooltip content={<ChartTooltipContent isRadialChart />} />\n\n <RadialBar\n isAnimationActive={false}\n dataKey=\"value\"\n cornerRadius={99}\n fill=\"currentColor\"\n background={{\n className: \"fill-utility-gray-100\",\n }}\n />\n\n {(title || subtitle) && (\n <text x=\"50%\" y=\"50%\" textAnchor=\"middle\" dominantBaseline=\"middle\">\n {subtitle && (\n <tspan\n x=\"50%\"\n dy={title ? \"-1.4em\" : \"1%\"}\n className={cx(\"fill-current text-tertiary\", \"text-sm font-medium\")}\n >\n {subtitle}\n </tspan>\n )}\n {title && (\n <tspan\n x=\"50%\"\n dy={subtitle ? \"1em\" : \"1%\"}\n className={cx(\"fill-current text-primary\", \"text-display-md font-semibold\")}\n >\n {title}\n </tspan>\n )}\n </text>\n )}\n </RadialBarChart>\n </ResponsiveContainer>\n );\n};\n","export type TasksCountResponse = {\n count: number;\n daysBack: number;\n since: string;\n};\n\ntype GetTasksCountOptions = {\n baseUrl?: string;\n moduleKey?: string;\n signal?: AbortSignal;\n};\n\nconst DEFAULT_API_BASE_URL = \"https://api.thesqd.com\";\nconst DEFAULT_MODULE_KEY = \"e166f127e73167b6646eeaeff8837566d3c27d4002fb122bc49d4fb06d97d67e\";\n\nexport const getTasksCount = async (daysBack: number, options: GetTasksCountOptions = {}) => {\n const resolvedDaysBack = Number.isFinite(daysBack) && daysBack > 0 ? Math.floor(daysBack) : 7;\n const baseUrl = options.baseUrl ?? process.env.NEXT_PUBLIC_SQD_API_BASE_URL ?? DEFAULT_API_BASE_URL;\n const moduleKey = options.moduleKey ?? process.env.NEXT_PUBLIC_SQD_MODULE_KEY ?? DEFAULT_MODULE_KEY;\n const url = new URL(\"/api/tasks/count\", baseUrl);\n\n url.searchParams.set(\"daysBack\", String(resolvedDaysBack));\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-sqd-module-key\": moduleKey,\n },\n cache: \"no-store\",\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch tasks count (${response.status})`);\n }\n\n const payload = (await response.json()) as TasksCountResponse;\n\n return payload.count ?? 0;\n};\n","\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport { getTasksCount } from \"@/api/tasks\";\nimport { ActivityGaugeLg } from \"@/components/application/charts/activity-gauge-lg\";\n\nexport type TasksActivityGaugeProps = {\n daysBack?: number;\n};\n\nexport const TasksActivityGauge = ({ daysBack = 7 }: TasksActivityGaugeProps) => {\n const [count, setCount] = useState<number | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const subtitle = useMemo(() => `Tasks last ${daysBack} days`, [daysBack]);\n\n useEffect(() => {\n let isActive = true;\n\n setError(null);\n setCount(null);\n\n getTasksCount(daysBack)\n .then((total) => {\n if (isActive) {\n setCount(total);\n }\n })\n .catch((err: unknown) => {\n if (isActive) {\n setError(err instanceof Error ? err.message : \"Failed to load tasks count\");\n }\n });\n\n return () => {\n isActive = false;\n };\n }, [daysBack]);\n\n const title = error ? \"—\" : count === null ? \"…\" : count.toLocaleString();\n\n return <ActivityGaugeLg title={title} subtitle={subtitle} />;\n};\n"]}
|