@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.
@@ -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 };
@@ -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":";;;;;;;AAEA,IAAM,UAAUA,iCAAA,CAAoB;AAAA,EAChC,MAAA,EAAQ;AAAA,IACJ,KAAA,EAAO;AAAA,MACH,MAAM,CAAC,YAAA,EAAc,cAAc,YAAA,EAAc,YAAA,EAAc,cAAc,aAAa;AAAA;AAC9F;AAER,CAAC,CAAA;AAMM,IAAM,EAAA,GAAK,OAAA;ACGX,IAAM,qBAAqB,CAAC,EAAE,OAAA,GAAU,IAAG,KAAmB;AACjE,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACtB,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,uBACIC,cAAA,CAAC,SAAI,SAAA,EAAU,4DAAA,EACV,kBAAQ,GAAA,CAAI,CAAC,OAAO,KAAA,KAAO;AAxBxC,IAAA,IAAA,EAAA,EAAA,EAAA;AAyBgB,IAAA,uBAAAC,eAAA,CAAC,KAAA,EAAA,EAA8C,WAAU,yBAAA,EACrD,QAAA,EAAA;AAAA,sBAAAD,cAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACG,SAAA,EAAW,EAAA;AAAA,YACP,yCAAA;AAAA,YAAA,CAAA,CACA,EAAA,GAAA,KAAA,CAAM,OAAA,KAAN,IAAA,GAAA,MAAA,GAAA,EAAA,CAAe,SAAA,IAAY,KAAA,CAAM,QAAQ,SAAA,CAAU,OAAA,CAAQ,OAAA,EAAS,KAAK,CAAA,GAAI;AAAA;AACjF;AAAA,OACJ;AAAA,sBACAA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oCAAA,EAAsC,gBAAM,KAAA,EAAM;AAAA,KAAA,EAAA,EAP5D,IAAG,EAAA,GAAA,KAAA,CAAM,KAAA,KAAN,YAAe,MAAM,CAAA,CAAA,EAAI,KAAK,CAAA,CAQ3C,CAAA;AAAA,EAAA,CACH,CAAA,EACL,CAAA;AAER,CAAA;AAMO,IAAM,sBAAsB,CAAC,EAAE,QAAQ,OAAA,EAAS,KAAA,EAAO,eAAc,KAAgC;AACxG,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,OAAA,IAAW,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC7C,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,uBACIC,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mEAAA,EACV,QAAA,EAAA;AAAA,IAAA,KAAA,oBAASD,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mCAAA,EAAqC,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,oBACpEA,cAAA,CAAC,SAAI,SAAA,EAAU,gBAAA,EACV,kBAAQ,GAAA,CAAI,CAAC,OAAO,KAAA,KAAO;AApD5C,MAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAqDoB,MAAA,uBAAAC,eAAA,CAAC,KAAA,EAAA,EAA8C,WAAU,iCAAA,EACrD,QAAA,EAAA;AAAA,wBAAAD,cAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACG,SAAA,EAAW,EAAA;AAAA,cACP,yCAAA;AAAA,cAAA,CAAA,CACA,EAAA,GAAA,KAAA,CAAM,OAAA,KAAN,IAAA,GAAA,MAAA,GAAA,EAAA,CAAe,SAAA,IAAY,KAAA,CAAM,QAAQ,SAAA,CAAU,OAAA,CAAQ,OAAA,EAAS,KAAK,CAAA,GAAI;AAAA;AACjF;AAAA,SACJ;AAAA,wBACAA,cAAA,CAAC,UAAK,SAAA,EAAU,gBAAA,EAAkB,sBAAM,IAAA,KAAN,IAAA,GAAA,EAAA,GAAe,aAAA,GAAgB,OAAA,GAAU,KAAA,EAAO,CAAA;AAAA,wBAClFA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,kCAAA,EAAoC,gBAAM,KAAA,EAAM;AAAA,OAAA,EAAA,EAR1D,IAAG,EAAA,GAAA,KAAA,CAAM,IAAA,KAAN,YAAc,OAAO,CAAA,CAAA,EAAI,KAAK,CAAA,CAS3C,CAAA;AAAA,IAAA,CACH,CAAA,EACL;AAAA,GAAA,EACJ,CAAA;AAER,CAAA;AC/CA,IAAM,UAAA,GAAmC;AAAA,EACrC;AAAA,IACI,IAAA,EAAM,UAAA;AAAA,IACN,KAAA,EAAO,GAAA;AAAA,IACP,SAAA,EAAW;AAAA,GACf;AAAA,EACA;AAAA,IACI,IAAA,EAAM,UAAA;AAAA,IACN,KAAA,EAAO,GAAA;AAAA,IACP,SAAA,EAAW;AAAA,GACf;AAAA,EACA;AAAA,IACI,IAAA,EAAM,UAAA;AAAA,IACN,KAAA,EAAO,GAAA;AAAA,IACP,SAAA,EAAW;AAAA;AAEnB,CAAA;AAEO,IAAM,kBAAkB,CAAC;AAAA,EAC5B,KAAA,GAAQ,OAAA;AAAA,EACR,QAAA,GAAW,cAAA;AAAA,EACX,IAAA,GAAO,UAAA;AAAA,EACP,QAAA,GAAW;AACf,CAAA,KAA0B;AACtB,EAAA,uBACIA,cAAAA,CAACE,4BAAA,EAAA,EAAoB,MAAA,EAAQ,KACzB,QAAA,kBAAAD,eAAAA;AAAA,IAACE,uBAAA;AAAA,IAAA;AAAA,MACG,IAAA;AAAA,MACA,kBAAA,EAAkB,IAAA;AAAA,MAClB,WAAA,EAAa,EAAA;AAAA,MACb,WAAA,EAAa,GAAA;AAAA,MACb,UAAA,EAAY,EAAA;AAAA,MACZ,UAAU,GAAA,GAAM,EAAA;AAAA,MAChB,SAAA,EAAU,qGAAA;AAAA,MACV,MAAA,EAAQ;AAAA,QACJ,IAAA,EAAM,CAAA;AAAA,QACN,KAAA,EAAO,CAAA;AAAA,QACP,GAAA,EAAK,CAAA;AAAA,QACL,MAAA,EAAQ;AAAA,OACZ;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAAH,cAAAA,CAACI,uBAAA,EAAA,EAAe,IAAA,EAAM,KAAA,EAAO,MAAA,EAAQ,CAAC,CAAA,EAAG,QAAQ,CAAA,EAAG,IAAA,EAAK,QAAA,EAAS,QAAA,EAAQ,IAAA,EAAC,CAAA;AAAA,wBAE3EJ,cAAAA,CAACK,eAAA,EAAA,EAAO,aAAA,EAAc,QAAA,EAAS,KAAA,EAAM,QAAA,EAAS,MAAA,EAAO,YAAA,EAAa,OAAA,kBAASL,cAAAA,CAAC,sBAAmB,CAAA,EAAI,CAAA;AAAA,wBAEnGA,eAACM,gBAAA,EAAA,EAAQ,OAAA,kBAASN,cAAAA,CAAC,mBAAA,EAAA,EAAoB,aAAA,EAAa,IAAA,EAAC,CAAA,EAAI,CAAA;AAAA,wBAEzDA,cAAAA;AAAA,UAACO,kBAAA;AAAA,UAAA;AAAA,YACG,iBAAA,EAAmB,KAAA;AAAA,YACnB,OAAA,EAAQ,OAAA;AAAA,YACR,YAAA,EAAc,EAAA;AAAA,YACd,IAAA,EAAK,cAAA;AAAA,YACL,UAAA,EAAY;AAAA,cACR,SAAA,EAAW;AAAA;AACf;AAAA,SACJ;AAAA,QAAA,CAEE,KAAA,IAAS,QAAA,qBACPN,eAAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,KAAA,EAAM,CAAA,EAAE,KAAA,EAAM,UAAA,EAAW,QAAA,EAAS,gBAAA,EAAiB,QAAA,EACtD,QAAA,EAAA;AAAA,UAAA,QAAA,oBACGD,cAAAA;AAAA,YAAC,OAAA;AAAA,YAAA;AAAA,cACG,CAAA,EAAE,KAAA;AAAA,cACF,EAAA,EAAI,QAAQ,QAAA,GAAW,IAAA;AAAA,cACvB,SAAA,EAAW,EAAA,CAAG,4BAAA,EAA8B,qBAAqB,CAAA;AAAA,cAEhE,QAAA,EAAA;AAAA;AAAA,WACL;AAAA,UAEH,yBACGA,cAAAA;AAAA,YAAC,OAAA;AAAA,YAAA;AAAA,cACG,CAAA,EAAE,KAAA;AAAA,cACF,EAAA,EAAI,WAAW,KAAA,GAAQ,IAAA;AAAA,cACvB,SAAA,EAAW,EAAA,CAAG,2BAAA,EAA6B,+BAA+B,CAAA;AAAA,cAEzE,QAAA,EAAA;AAAA;AAAA;AACL,SAAA,EAER;AAAA;AAAA;AAAA,GAER,EACJ,CAAA;AAER;;;AC1FA,IAAM,oBAAA,GAAuB,wBAAA;AAC7B,IAAM,kBAAA,GAAqB,kEAAA;AAEpB,IAAM,aAAA,GAAgB,OAAO,QAAA,EAAkB,OAAA,GAAgC,EAAC,KAAM;AAf7F,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAgBI,EAAA,MAAM,gBAAA,GAAmB,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,IAAK,WAAW,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA,GAAI,CAAA;AAC5F,EAAA,MAAM,WAAU,EAAA,GAAA,CAAA,EAAA,GAAA,OAAA,CAAQ,OAAA,KAAR,YAAmB,OAAA,CAAQ,GAAA,CAAI,iCAA/B,IAAA,GAAA,EAAA,GAA+D,oBAAA;AAC/E,EAAA,MAAM,aAAY,EAAA,GAAA,CAAA,EAAA,GAAA,OAAA,CAAQ,SAAA,KAAR,YAAqB,OAAA,CAAQ,GAAA,CAAI,+BAAjC,IAAA,GAAA,EAAA,GAA+D,kBAAA;AACjF,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,kBAAA,EAAoB,OAAO,CAAA;AAE/C,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAA,EAAY,MAAA,CAAO,gBAAgB,CAAC,CAAA;AAEzD,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,CAAI,UAAS,EAAG;AAAA,IACzC,MAAA,EAAQ,KAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACL,cAAA,EAAgB,kBAAA;AAAA,MAChB,kBAAA,EAAoB;AAAA,KACxB;AAAA,IACA,KAAA,EAAO,UAAA;AAAA,IACP,QAAQ,OAAA,CAAQ;AAAA,GACnB,CAAA;AAED,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,QAAA,CAAS,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,EACtE;AAEA,EAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AAErC,EAAA,OAAA,CAAO,EAAA,GAAA,OAAA,CAAQ,UAAR,IAAA,GAAA,EAAA,GAAiB,CAAA;AAC5B;AC7BO,IAAM,kBAAA,GAAqB,CAAC,EAAE,QAAA,GAAW,GAAE,KAA+B;AAC7E,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIQ,eAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAwB,IAAI,CAAA;AAEtD,EAAA,MAAM,QAAA,GAAWC,cAAQ,MAAM,CAAA,WAAA,EAAc,QAAQ,CAAA,KAAA,CAAA,EAAS,CAAC,QAAQ,CAAC,CAAA;AAExE,EAAAC,eAAA,CAAU,MAAM;AACZ,IAAA,IAAI,QAAA,GAAW,IAAA;AAEf,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,QAAA,CAAS,IAAI,CAAA;AAEb,IAAA,aAAA,CAAc,QAAQ,CAAA,CACjB,IAAA,CAAK,CAAC,KAAA,KAAU;AACb,MAAA,IAAI,QAAA,EAAU;AACV,QAAA,QAAA,CAAS,KAAK,CAAA;AAAA,MAClB;AAAA,IACJ,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KAAiB;AACrB,MAAA,IAAI,QAAA,EAAU;AACV,QAAA,QAAA,CAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,4BAA4B,CAAA;AAAA,MAC9E;AAAA,IACJ,CAAC,CAAA;AAEL,IAAA,OAAO,MAAM;AACT,MAAA,QAAA,GAAW,KAAA;AAAA,IACf,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,QAAQ,KAAA,GAAQ,QAAA,GAAM,UAAU,IAAA,GAAO,QAAA,GAAM,MAAM,cAAA,EAAe;AAExE,EAAA,uBAAOV,cAAAA,CAAC,eAAA,EAAA,EAAgB,KAAA,EAAc,QAAA,EAAoB,CAAA;AAC9D","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"]}