@openrails/contentkit-ui 0.103.2 → 0.104.0-rc.1
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 +105 -43
- package/dist/client/beacons.d.ts +3 -13
- package/dist/client/beacons.js +4 -45
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +45 -43
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/favorites.d.ts +2 -0
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/types.d.ts +3 -0
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/discovery.d.ts +28 -3
- package/dist/client/discovery.js +22 -1
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/engagement.d.ts +70 -0
- package/dist/client/engagement.js +191 -0
- package/dist/client/engagement.js.map +1 -0
- package/dist/client/generated/routes.d.ts +35 -9
- package/dist/client/generated/routes.js +59 -2
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +53 -12
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/guest-reading-history.d.ts +40 -0
- package/dist/client/guest-reading-history.js +144 -0
- package/dist/client/guest-reading-history.js.map +1 -0
- package/dist/client/index.d.ts +4 -2
- package/dist/client/stats.d.ts +5 -5
- package/dist/client/stats.js.map +1 -1
- package/dist/client.js +24 -22
- package/dist/components/admin/content-manager.js +30 -32
- package/dist/components/admin/content-manager.js.map +1 -1
- package/dist/components/admin/quick-search.d.ts +14 -0
- package/dist/components/admin/quick-search.js +60 -0
- package/dist/components/admin/quick-search.js.map +1 -0
- package/dist/components/admin/reader-content-editor.js +452 -428
- package/dist/components/admin/reader-content-editor.js.map +1 -1
- package/dist/components/admin/stats-dashboard.d.ts +4 -4
- package/dist/components/admin/stats-dashboard.js +132 -134
- package/dist/components/admin/stats-dashboard.js.map +1 -1
- package/dist/components/content/daily-releases.js +143 -191
- package/dist/components/content/daily-releases.js.map +1 -1
- package/dist/components/content/favorites.d.ts +6 -0
- package/dist/components/content/favorites.js +33 -0
- package/dist/components/content/favorites.js.map +1 -0
- package/dist/components/content/filter-dialog.js +19 -15
- package/dist/components/content/filter-dialog.js.map +1 -1
- package/dist/components/content/filters.d.ts +2 -0
- package/dist/components/content/filters.js +5 -1
- package/dist/components/content/filters.js.map +1 -1
- package/dist/components/content/grid.d.ts +34 -13
- package/dist/components/content/grid.js +300 -271
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/history.d.ts +6 -0
- package/dist/components/content/history.js +91 -0
- package/dist/components/content/history.js.map +1 -0
- package/dist/components/content/index.d.ts +1 -0
- package/dist/components/content/search.d.ts +11 -4
- package/dist/components/content/search.js +22 -11
- package/dist/components/content/search.js.map +1 -1
- package/dist/components/content/summary-tile.d.ts +14 -0
- package/dist/components/content/summary-tile.js +63 -0
- package/dist/components/content/summary-tile.js.map +1 -0
- package/dist/components/media-folder-editor.js +1 -1
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/video-mini-player.d.ts +4 -1
- package/dist/components/video-mini-player.js +77 -71
- package/dist/components/video-mini-player.js.map +1 -1
- package/dist/components/video-player.d.ts +4 -1
- package/dist/components/video-player.js +280 -276
- package/dist/components/video-player.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -14
- package/dist/locales/admin/de.js +8 -8
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +4 -4
- package/dist/locales/admin/en.js +8 -8
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +8 -8
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +8 -8
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +8 -8
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +8 -8
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/discovery/de.js +0 -1
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +0 -1
- package/dist/locales/discovery/en.js +0 -1
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +0 -1
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +0 -1
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +0 -1
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +0 -1
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +4 -5
- package/dist/react/discovery.d.ts +20 -17
- package/dist/react/discovery.js +273 -221
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/resources.d.ts +3 -1
- package/dist/react/resources.js +4 -0
- package/dist/react/resources.js.map +1 -1
- package/dist/react/stats.d.ts +2 -2
- package/dist/react/stats.js.map +1 -1
- package/dist/react/use-engagement.d.ts +26 -0
- package/dist/react/use-engagement.js +221 -0
- package/dist/react/use-engagement.js.map +1 -0
- package/dist/react/use-resource.d.ts +4 -3
- package/dist/react/use-resource.js +2 -1
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +12 -11
- package/dist/styles.css +1 -1
- package/dist/ui/dialog.d.ts +2 -1
- package/dist/ui/dialog.js +23 -20
- package/dist/ui/dialog.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,228 +1,226 @@
|
|
|
1
1
|
import { useMessages as e } from "../../i18n/context.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { formatDuration as t } from "../../client/gallery.js";
|
|
3
|
+
import { Select as n, SelectContent as r, SelectItem as i, SelectTrigger as a, SelectValue as o } from "../../ui/select.js";
|
|
4
|
+
import { AdminRoot as s, LoadFailed as c, NoAccess as l, Rows as u } from "./shell.js";
|
|
5
|
+
import { useFlowClient as d } from "../../react/admin.js";
|
|
6
|
+
import { useActivity as f, useTopContents as p } from "../../react/stats.js";
|
|
7
|
+
import { useState as m } from "react";
|
|
8
|
+
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
8
9
|
//#region src/components/admin/stats-dashboard.tsx
|
|
9
|
-
var
|
|
10
|
+
var v = (e) => e.map((e) => typeof e == "string" ? {
|
|
10
11
|
value: e,
|
|
11
12
|
label: e
|
|
12
|
-
} : e),
|
|
13
|
+
} : e), y = [
|
|
13
14
|
7,
|
|
14
15
|
30,
|
|
15
16
|
90
|
|
16
|
-
],
|
|
17
|
+
], b = ["readers", "comments"], x = [
|
|
17
18
|
"7d",
|
|
18
19
|
"30d",
|
|
19
20
|
"90d",
|
|
20
21
|
"365d",
|
|
21
22
|
"all"
|
|
22
|
-
],
|
|
23
|
-
function
|
|
24
|
-
let
|
|
23
|
+
], S = (e) => e?.code === "forbidden" || e?.code === "unauthorized";
|
|
24
|
+
function C({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className: a, language: o, messages: c, ...l }) {
|
|
25
|
+
let u = d({
|
|
25
26
|
client: e,
|
|
26
27
|
baseUrl: t,
|
|
27
28
|
auth: n
|
|
28
29
|
});
|
|
29
|
-
return /* @__PURE__ */
|
|
30
|
+
return /* @__PURE__ */ g(s, {
|
|
30
31
|
flow: "stats-dashboard",
|
|
31
32
|
appearance: i,
|
|
32
33
|
className: a,
|
|
33
|
-
language:
|
|
34
|
+
language: o,
|
|
34
35
|
messages: c,
|
|
35
36
|
guard: r,
|
|
36
|
-
children: /* @__PURE__ */
|
|
37
|
+
children: /* @__PURE__ */ g(w, {
|
|
37
38
|
...l,
|
|
38
|
-
client:
|
|
39
|
+
client: u
|
|
39
40
|
})
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
|
-
function
|
|
43
|
-
let { t:
|
|
44
|
-
kind:
|
|
45
|
-
days:
|
|
46
|
-
} : null, { client:
|
|
47
|
-
kind:
|
|
48
|
-
metric:
|
|
49
|
-
period:
|
|
50
|
-
limit:
|
|
51
|
-
} : null, { client:
|
|
52
|
-
if (
|
|
53
|
-
let
|
|
54
|
-
return /* @__PURE__ */
|
|
55
|
-
/* @__PURE__ */
|
|
43
|
+
function w({ kinds: n, contentHref: r, limit: i = 10, client: a }) {
|
|
44
|
+
let { t: o } = e(), s = v(n), [d, C] = m(s[0]?.value ?? ""), [w, E] = m(7), [D, O] = m("readers"), [k, A] = m("30d"), j = f(d ? {
|
|
45
|
+
kind: d,
|
|
46
|
+
days: w
|
|
47
|
+
} : null, { client: a }), M = p(d ? {
|
|
48
|
+
kind: d,
|
|
49
|
+
metric: D,
|
|
50
|
+
period: k,
|
|
51
|
+
limit: i
|
|
52
|
+
} : null, { client: a });
|
|
53
|
+
if (S(j.error) || S(M.error)) return /* @__PURE__ */ g(l, { children: o("stats.noAccess") });
|
|
54
|
+
let N = j.data?.days ?? [], P = Math.max(1, ...N.map((e) => e.readers)), F = N.reduce((e, t) => e + t.active_ms, 0);
|
|
55
|
+
return /* @__PURE__ */ _(h, { children: [
|
|
56
|
+
/* @__PURE__ */ _("div", {
|
|
56
57
|
className: "flex flex-wrap items-center justify-between gap-2",
|
|
57
|
-
children: [/* @__PURE__ */
|
|
58
|
+
children: [/* @__PURE__ */ g("h2", {
|
|
58
59
|
className: "text-base font-semibold",
|
|
59
|
-
children:
|
|
60
|
-
}),
|
|
61
|
-
label:
|
|
62
|
-
value:
|
|
63
|
-
onChange:
|
|
64
|
-
options:
|
|
60
|
+
children: o("stats.title")
|
|
61
|
+
}), s.length > 1 && /* @__PURE__ */ g(T, {
|
|
62
|
+
label: o("stats.kind"),
|
|
63
|
+
value: d,
|
|
64
|
+
onChange: C,
|
|
65
|
+
options: s
|
|
65
66
|
})]
|
|
66
67
|
}),
|
|
67
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ _("section", {
|
|
68
69
|
className: "grid gap-3",
|
|
69
70
|
"data-ckui": "stats-activity",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
71
|
+
children: [/* @__PURE__ */ _("div", {
|
|
71
72
|
className: "flex flex-wrap items-center justify-between gap-2",
|
|
72
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ g("h3", {
|
|
73
74
|
className: "font-medium",
|
|
74
|
-
children:
|
|
75
|
-
}), /* @__PURE__ */
|
|
76
|
-
label:
|
|
77
|
-
value: String(
|
|
78
|
-
onChange: (e) =>
|
|
79
|
-
options:
|
|
75
|
+
children: o("stats.activity")
|
|
76
|
+
}), /* @__PURE__ */ g(T, {
|
|
77
|
+
label: o("stats.days"),
|
|
78
|
+
value: String(w),
|
|
79
|
+
onChange: (e) => E(Number(e)),
|
|
80
|
+
options: y.map((e) => ({
|
|
80
81
|
value: String(e),
|
|
81
|
-
label:
|
|
82
|
+
label: o("stats.lastDays", { count: e })
|
|
82
83
|
}))
|
|
83
84
|
})]
|
|
84
|
-
}),
|
|
85
|
+
}), j.unavailable ? /* @__PURE__ */ g("p", {
|
|
85
86
|
className: "text-muted-foreground",
|
|
86
87
|
role: "status",
|
|
87
|
-
children:
|
|
88
|
-
}) :
|
|
89
|
-
error:
|
|
90
|
-
message:
|
|
91
|
-
retry:
|
|
92
|
-
}) :
|
|
88
|
+
children: o("stats.unavailable")
|
|
89
|
+
}) : j.error ? /* @__PURE__ */ g(c, {
|
|
90
|
+
error: j.error,
|
|
91
|
+
message: o("stats.loadFailed"),
|
|
92
|
+
retry: j.reload
|
|
93
|
+
}) : j.loading && !j.data ? /* @__PURE__ */ g(u, { count: 2 }) : N.length ? /* @__PURE__ */ _("table", {
|
|
93
94
|
className: "w-full text-left tabular-nums",
|
|
94
|
-
"aria-busy":
|
|
95
|
+
"aria-busy": j.loading,
|
|
95
96
|
children: [
|
|
96
|
-
/* @__PURE__ */
|
|
97
|
+
/* @__PURE__ */ g("thead", {
|
|
97
98
|
className: "text-xs text-muted-foreground",
|
|
98
|
-
children: /* @__PURE__ */
|
|
99
|
-
/* @__PURE__ */
|
|
99
|
+
children: /* @__PURE__ */ _("tr", { children: [
|
|
100
|
+
/* @__PURE__ */ g("th", {
|
|
100
101
|
className: "py-1 font-medium",
|
|
101
|
-
children:
|
|
102
|
+
children: o("stats.date")
|
|
102
103
|
}),
|
|
103
|
-
/* @__PURE__ */
|
|
104
|
+
/* @__PURE__ */ g("th", {
|
|
104
105
|
className: "py-1 font-medium",
|
|
105
|
-
children:
|
|
106
|
+
children: o("stats.readers")
|
|
106
107
|
}),
|
|
107
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ g("th", {
|
|
108
109
|
className: "py-1 text-end font-medium",
|
|
109
|
-
children:
|
|
110
|
+
children: o("stats.users")
|
|
110
111
|
}),
|
|
111
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ g("th", {
|
|
112
113
|
className: "py-1 text-end font-medium",
|
|
113
|
-
children:
|
|
114
|
+
children: o("stats.activeTime")
|
|
114
115
|
})
|
|
115
116
|
] })
|
|
116
117
|
}),
|
|
117
|
-
/* @__PURE__ */
|
|
118
|
+
/* @__PURE__ */ g("tbody", { children: N.map((e) => /* @__PURE__ */ _("tr", {
|
|
118
119
|
className: "border-t border-border",
|
|
119
120
|
"data-ckui": "stats-day",
|
|
120
121
|
children: [
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ g("td", {
|
|
122
123
|
className: "py-1.5 pe-3 whitespace-nowrap",
|
|
123
124
|
children: e.date
|
|
124
125
|
}),
|
|
125
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ g("td", {
|
|
126
127
|
className: "w-1/2 py-1.5 pe-3",
|
|
127
|
-
children: /* @__PURE__ */
|
|
128
|
+
children: /* @__PURE__ */ _("span", {
|
|
128
129
|
className: "flex items-center gap-2",
|
|
129
|
-
children: [/* @__PURE__ */
|
|
130
|
+
children: [/* @__PURE__ */ g("span", {
|
|
130
131
|
"aria-hidden": !0,
|
|
131
132
|
className: "h-2 rounded-full bg-primary",
|
|
132
|
-
style: { width: `${Math.max(2, e.
|
|
133
|
-
}), e.
|
|
133
|
+
style: { width: `${Math.max(2, e.readers / P * 100)}%` }
|
|
134
|
+
}), e.readers]
|
|
134
135
|
})
|
|
135
136
|
}),
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ g("td", {
|
|
137
138
|
className: "py-1.5 text-end",
|
|
138
|
-
children: e.
|
|
139
|
+
children: e.users
|
|
139
140
|
}),
|
|
140
|
-
/* @__PURE__ */
|
|
141
|
+
/* @__PURE__ */ g("td", {
|
|
141
142
|
className: "py-1.5 text-end",
|
|
142
|
-
children: e.
|
|
143
|
+
children: t(Math.floor(e.active_ms / 1e3))
|
|
143
144
|
})
|
|
144
145
|
]
|
|
145
146
|
}, e.date)) }),
|
|
146
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ g("tfoot", { children: /* @__PURE__ */ _("tr", {
|
|
147
148
|
className: "border-t border-border font-medium",
|
|
148
|
-
children: [
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/* @__PURE__ */ h("td", {}),
|
|
159
|
-
/* @__PURE__ */ h("td", {})
|
|
160
|
-
]
|
|
149
|
+
children: [/* @__PURE__ */ g("th", {
|
|
150
|
+
scope: "row",
|
|
151
|
+
colSpan: 3,
|
|
152
|
+
className: "py-1.5 font-medium",
|
|
153
|
+
children: o("stats.totalActiveTime")
|
|
154
|
+
}), /* @__PURE__ */ g("td", {
|
|
155
|
+
className: "py-1.5 text-end",
|
|
156
|
+
"data-ckui": "stats-active-total",
|
|
157
|
+
children: t(Math.floor(F / 1e3))
|
|
158
|
+
})]
|
|
161
159
|
}) })
|
|
162
160
|
]
|
|
163
|
-
}) : /* @__PURE__ */
|
|
161
|
+
}) : /* @__PURE__ */ g("p", {
|
|
164
162
|
className: "text-muted-foreground",
|
|
165
|
-
children:
|
|
163
|
+
children: o("stats.noActivity")
|
|
166
164
|
})]
|
|
167
165
|
}),
|
|
168
|
-
/* @__PURE__ */
|
|
166
|
+
/* @__PURE__ */ _("section", {
|
|
169
167
|
className: "grid gap-3",
|
|
170
168
|
"data-ckui": "stats-top",
|
|
171
|
-
children: [/* @__PURE__ */
|
|
169
|
+
children: [/* @__PURE__ */ _("div", {
|
|
172
170
|
className: "flex flex-wrap items-center justify-between gap-2",
|
|
173
|
-
children: [/* @__PURE__ */
|
|
171
|
+
children: [/* @__PURE__ */ g("h3", {
|
|
174
172
|
className: "font-medium",
|
|
175
|
-
children:
|
|
176
|
-
}), /* @__PURE__ */
|
|
173
|
+
children: o("stats.top")
|
|
174
|
+
}), /* @__PURE__ */ _("div", {
|
|
177
175
|
className: "flex flex-wrap gap-2",
|
|
178
|
-
children: [/* @__PURE__ */
|
|
179
|
-
label:
|
|
180
|
-
value:
|
|
181
|
-
onChange: (e) =>
|
|
182
|
-
options:
|
|
176
|
+
children: [/* @__PURE__ */ g(T, {
|
|
177
|
+
label: o("stats.metric"),
|
|
178
|
+
value: D,
|
|
179
|
+
onChange: (e) => O(e),
|
|
180
|
+
options: b.map((e) => ({
|
|
183
181
|
value: e,
|
|
184
|
-
label:
|
|
182
|
+
label: o(`stats.metric_${e}`)
|
|
185
183
|
}))
|
|
186
|
-
}), /* @__PURE__ */
|
|
187
|
-
label:
|
|
188
|
-
value:
|
|
189
|
-
onChange: (e) =>
|
|
190
|
-
options:
|
|
184
|
+
}), /* @__PURE__ */ g(T, {
|
|
185
|
+
label: o("stats.period"),
|
|
186
|
+
value: k,
|
|
187
|
+
onChange: (e) => A(e),
|
|
188
|
+
options: x.map((e) => ({
|
|
191
189
|
value: e,
|
|
192
|
-
label:
|
|
190
|
+
label: o(`stats.period_${e}`)
|
|
193
191
|
}))
|
|
194
192
|
})]
|
|
195
193
|
})]
|
|
196
|
-
}),
|
|
194
|
+
}), M.unavailable ? /* @__PURE__ */ g("p", {
|
|
197
195
|
className: "text-muted-foreground",
|
|
198
196
|
role: "status",
|
|
199
|
-
children:
|
|
200
|
-
}) :
|
|
201
|
-
error:
|
|
202
|
-
message:
|
|
203
|
-
retry:
|
|
204
|
-
}) :
|
|
197
|
+
children: o("stats.unavailable")
|
|
198
|
+
}) : M.error ? /* @__PURE__ */ g(c, {
|
|
199
|
+
error: M.error,
|
|
200
|
+
message: o("stats.loadFailed"),
|
|
201
|
+
retry: M.reload
|
|
202
|
+
}) : M.loading && !M.data ? /* @__PURE__ */ g(u, { count: 3 }) : M.data?.contents.length ? /* @__PURE__ */ g("ol", {
|
|
205
203
|
className: "grid divide-y divide-border rounded-lg border border-border",
|
|
206
|
-
"aria-busy":
|
|
207
|
-
children:
|
|
208
|
-
let
|
|
209
|
-
return /* @__PURE__ */
|
|
204
|
+
"aria-busy": M.loading,
|
|
205
|
+
children: M.data.contents.map((e, t) => {
|
|
206
|
+
let n = r ? r(e) : e.path;
|
|
207
|
+
return /* @__PURE__ */ _("li", {
|
|
210
208
|
className: "flex items-center gap-3 p-2.5",
|
|
211
209
|
"data-ckui": "stats-ranked",
|
|
212
210
|
children: [
|
|
213
|
-
/* @__PURE__ */
|
|
211
|
+
/* @__PURE__ */ g("span", {
|
|
214
212
|
className: "w-6 text-end text-muted-foreground tabular-nums",
|
|
215
213
|
children: t + 1
|
|
216
214
|
}),
|
|
217
|
-
|
|
215
|
+
n ? /* @__PURE__ */ g("a", {
|
|
218
216
|
className: "min-w-0 flex-1 break-words hover:underline",
|
|
219
|
-
href:
|
|
217
|
+
href: n,
|
|
220
218
|
children: e.title
|
|
221
|
-
}) : /* @__PURE__ */
|
|
219
|
+
}) : /* @__PURE__ */ g("span", {
|
|
222
220
|
className: "min-w-0 flex-1 break-words",
|
|
223
221
|
children: e.title
|
|
224
222
|
}),
|
|
225
|
-
/* @__PURE__ */
|
|
223
|
+
/* @__PURE__ */ g("span", {
|
|
226
224
|
className: "tabular-nums",
|
|
227
225
|
"data-ckui": "stats-value",
|
|
228
226
|
children: e.value
|
|
@@ -230,28 +228,28 @@ function C({ kinds: t, contentHref: n, limit: r = 10, client: i }) {
|
|
|
230
228
|
]
|
|
231
229
|
}, e.id);
|
|
232
230
|
})
|
|
233
|
-
}) : /* @__PURE__ */
|
|
231
|
+
}) : /* @__PURE__ */ g("p", {
|
|
234
232
|
className: "text-muted-foreground",
|
|
235
|
-
children:
|
|
233
|
+
children: o("stats.noTop")
|
|
236
234
|
})]
|
|
237
235
|
})
|
|
238
236
|
] });
|
|
239
237
|
}
|
|
240
|
-
function
|
|
241
|
-
return /* @__PURE__ */
|
|
242
|
-
value:
|
|
238
|
+
function T({ label: e, value: t, onChange: s, options: c }) {
|
|
239
|
+
return /* @__PURE__ */ _(n, {
|
|
240
|
+
value: t,
|
|
243
241
|
onValueChange: (e) => s(String(e)),
|
|
244
|
-
children: [/* @__PURE__ */
|
|
242
|
+
children: [/* @__PURE__ */ g(a, {
|
|
245
243
|
size: "sm",
|
|
246
244
|
"aria-label": e,
|
|
247
|
-
children: /* @__PURE__ */
|
|
248
|
-
}), /* @__PURE__ */
|
|
245
|
+
children: /* @__PURE__ */ g(o, { children: (e) => c.find((t) => t.value === e)?.label ?? e })
|
|
246
|
+
}), /* @__PURE__ */ g(r, { children: c.map((e) => /* @__PURE__ */ g(i, {
|
|
249
247
|
value: e.value,
|
|
250
248
|
children: e.label
|
|
251
249
|
}, e.value)) })]
|
|
252
250
|
});
|
|
253
251
|
}
|
|
254
252
|
//#endregion
|
|
255
|
-
export {
|
|
253
|
+
export { C as StatsDashboard };
|
|
256
254
|
|
|
257
255
|
//# sourceMappingURL=stats-dashboard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stats-dashboard.js","names":[],"sources":["../../../src/components/admin/stats-dashboard.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentKitError } from \"../../client/errors.js\";\nimport type { Period } from \"../../client/discovery.js\";\nimport type { RankedContent } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useActivity, useTopContents } from \"../../react/stats.js\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\ntype Metric = \"views\" | \"comments\";\n\nexport interface StatsDashboardProps extends AdminFlowProps {\n /** Content kinds offered: codes, or codes with labels; the first shows first. */\n kinds: readonly Choice[];\n /** A ranked item's link; default its path. */\n contentHref?: (content: RankedContent) => string | undefined;\n /** Rows in the ranking. Default 10. */\n limit?: number;\n}\n\nconst choices = (list: readonly Choice[]) => list.map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst DAYS = [7, 30, 90] as const;\nconst METRICS: readonly Metric[] = [\"views\", \"comments\"];\nconst PERIODS: readonly Period[] = [\"7d\", \"30d\", \"90d\", \"365d\", \"all\"];\nconst refused = (e?: ContentKitError) => e?.code === \"forbidden\" || e?.code === \"unauthorized\";\n\n/**\n * Content statistics for staff (root:content:view-restricted): a kind's views\n * per day (view sessions, distinct and signed-in viewers) and its content\n * ranked by views or comments over a period. Views need the signal plane;\n * without it the dashboard says so and still ranks by comments.\n */\nexport function StatsDashboard({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: StatsDashboardProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"stats-dashboard\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Dashboard {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<StatsDashboardProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Dashboard({ kinds, contentHref, limit = 10, client }: Inner) {\n const { t } = useMessages();\n const options = choices(kinds);\n const [kind, setKind] = useState(options[0]?.value ?? \"\");\n const [days, setDays] = useState<number>(7);\n const [metric, setMetric] = useState<Metric>(\"views\");\n const [period, setPeriod] = useState<Period>(\"30d\");\n const activity = useActivity(kind ? { kind, days } : null, { client });\n const top = useTopContents(kind ? { kind, metric, period, limit } : null, { client });\n if (refused(activity.error) || refused(top.error)) return <NoAccess>{t(\"stats.noAccess\")}</NoAccess>;\n const rows = activity.data?.days ?? [];\n const most = Math.max(1, ...rows.map((d) => d.views));\n const total = rows.reduce((n, d) => n + d.views, 0);\n return (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"stats.title\")}</h2>\n {options.length > 1 && (\n <Pick label={t(\"stats.kind\")} value={kind} onChange={setKind} options={options} />\n )}\n </div>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-activity\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.activity\")}</h3>\n <Pick label={t(\"stats.days\")} value={String(days)} onChange={(v) => setDays(Number(v))} options={DAYS.map((d) => ({ value: String(d), label: t(\"stats.lastDays\", { count: d }) }))} />\n </div>\n {activity.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : activity.error ? (\n <LoadFailed error={activity.error} message={t(\"stats.loadFailed\")} retry={activity.reload} />\n ) : activity.loading && !activity.data ? (\n <Rows count={2} />\n ) : !rows.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noActivity\")}</p>\n ) : (\n <table className=\"w-full text-left tabular-nums\" aria-busy={activity.loading}>\n <thead className=\"text-xs text-muted-foreground\">\n <tr>\n <th className=\"py-1 font-medium\">{t(\"stats.date\")}</th>\n <th className=\"py-1 font-medium\">{t(\"stats.views\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.viewers\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.users\")}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((d) => (\n <tr key={d.date} className=\"border-t border-border\" data-ckui=\"stats-day\">\n <td className=\"py-1.5 pe-3 whitespace-nowrap\">{d.date}</td>\n <td className=\"w-1/2 py-1.5 pe-3\">\n <span className=\"flex items-center gap-2\">\n <span aria-hidden className=\"h-2 rounded-full bg-primary\" style={{ width: `${Math.max(2, (d.views / most) * 100)}%` }} />\n {d.views}\n </span>\n </td>\n <td className=\"py-1.5 text-end\">{d.viewers}</td>\n <td className=\"py-1.5 text-end\">{d.users}</td>\n </tr>\n ))}\n </tbody>\n <tfoot>\n <tr className=\"border-t border-border font-medium\">\n <td className=\"py-1.5\">{t(\"stats.total\")}</td>\n <td className=\"py-1.5\" data-ckui=\"stats-total\">\n {total}\n </td>\n <td />\n <td />\n </tr>\n </tfoot>\n </table>\n )}\n </section>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-top\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.top\")}</h3>\n <div className=\"flex flex-wrap gap-2\">\n <Pick label={t(\"stats.metric\")} value={metric} onChange={(v) => setMetric(v as Metric)} options={METRICS.map((x) => ({ value: x, label: t(`stats.metric_${x}`) }))} />\n <Pick label={t(\"stats.period\")} value={period} onChange={(v) => setPeriod(v as Period)} options={PERIODS.map((x) => ({ value: x, label: t(`stats.period_${x}`) }))} />\n </div>\n </div>\n {top.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : top.error ? (\n <LoadFailed error={top.error} message={t(\"stats.loadFailed\")} retry={top.reload} />\n ) : top.loading && !top.data ? (\n <Rows count={3} />\n ) : !top.data?.contents.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noTop\")}</p>\n ) : (\n <ol className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={top.loading}>\n {top.data.contents.map((c, i) => {\n const href = contentHref ? contentHref(c) : c.path;\n return (\n <li key={c.id} className=\"flex items-center gap-3 p-2.5\" data-ckui=\"stats-ranked\">\n <span className=\"w-6 text-end text-muted-foreground tabular-nums\">{i + 1}</span>\n {href ? (\n <a className=\"min-w-0 flex-1 break-words hover:underline\" href={href}>\n {c.title}\n </a>\n ) : (\n <span className=\"min-w-0 flex-1 break-words\">{c.title}</span>\n )}\n <span className=\"tabular-nums\" data-ckui=\"stats-value\">\n {c.value}\n </span>\n </li>\n );\n })}\n </ol>\n )}\n </section>\n </>\n );\n}\n\nfunction Pick({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: { value: string; label: string }[] }) {\n return (\n <Select value={value} onValueChange={(v) => onChange(String(v))}>\n <SelectTrigger size=\"sm\" aria-label={label}>\n <SelectValue>{(v: string) => options.find((o) => o.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stats-dashboard.js","names":[],"sources":["../../../src/components/admin/stats-dashboard.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentKitError } from \"../../client/errors.js\";\nimport type { Period } from \"../../client/discovery.js\";\nimport { formatDuration } from \"../../client/gallery.js\";\nimport type { TopQuery } from \"../../client/stats.js\";\nimport type { RankedContent } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useActivity, useTopContents } from \"../../react/stats.js\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\ntype Metric = NonNullable<TopQuery[\"metric\"]>;\n\nexport interface StatsDashboardProps extends AdminFlowProps {\n /** Content kinds offered: codes, or codes with labels; the first shows first. */\n kinds: readonly Choice[];\n /** A ranked item's link; default its path. */\n contentHref?: (content: RankedContent) => string | undefined;\n /** Rows in the ranking. Default 10. */\n limit?: number;\n}\n\nconst choices = (list: readonly Choice[]) => list.map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst DAYS = [7, 30, 90] as const;\nconst METRICS: readonly Metric[] = [\"readers\", \"comments\"];\nconst PERIODS: readonly Period[] = [\"7d\", \"30d\", \"90d\", \"365d\", \"all\"];\nconst refused = (e?: ContentKitError) => e?.code === \"forbidden\" || e?.code === \"unauthorized\";\n\n/**\n * Staff statistics from measured attention: daily distinct people and focused\n * active time, plus content ranked by distinct engaged people or comments.\n * Daily people are never summed into a misleading period-unique total.\n * Comments remain available without the signal plane.\n */\nexport function StatsDashboard({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: StatsDashboardProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"stats-dashboard\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Dashboard {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<StatsDashboardProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Dashboard({ kinds, contentHref, limit = 10, client }: Inner) {\n const { t } = useMessages();\n const options = choices(kinds);\n const [kind, setKind] = useState(options[0]?.value ?? \"\");\n const [days, setDays] = useState<number>(7);\n const [metric, setMetric] = useState<Metric>(\"readers\");\n const [period, setPeriod] = useState<Period>(\"30d\");\n const activity = useActivity(kind ? { kind, days } : null, { client });\n const top = useTopContents(kind ? { kind, metric, period, limit } : null, { client });\n if (refused(activity.error) || refused(top.error)) return <NoAccess>{t(\"stats.noAccess\")}</NoAccess>;\n const rows = activity.data?.days ?? [];\n const most = Math.max(1, ...rows.map((d) => d.readers));\n const totalActiveMS = rows.reduce((n, d) => n + d.active_ms, 0);\n return (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"stats.title\")}</h2>\n {options.length > 1 && <Pick label={t(\"stats.kind\")} value={kind} onChange={setKind} options={options} />}\n </div>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-activity\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.activity\")}</h3>\n <Pick\n label={t(\"stats.days\")}\n value={String(days)}\n onChange={(v) => setDays(Number(v))}\n options={DAYS.map((d) => ({ value: String(d), label: t(\"stats.lastDays\", { count: d }) }))}\n />\n </div>\n {activity.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : activity.error ? (\n <LoadFailed error={activity.error} message={t(\"stats.loadFailed\")} retry={activity.reload} />\n ) : activity.loading && !activity.data ? (\n <Rows count={2} />\n ) : !rows.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noActivity\")}</p>\n ) : (\n <table className=\"w-full text-left tabular-nums\" aria-busy={activity.loading}>\n <thead className=\"text-xs text-muted-foreground\">\n <tr>\n <th className=\"py-1 font-medium\">{t(\"stats.date\")}</th>\n <th className=\"py-1 font-medium\">{t(\"stats.readers\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.users\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.activeTime\")}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((d) => (\n <tr key={d.date} className=\"border-t border-border\" data-ckui=\"stats-day\">\n <td className=\"py-1.5 pe-3 whitespace-nowrap\">{d.date}</td>\n <td className=\"w-1/2 py-1.5 pe-3\">\n <span className=\"flex items-center gap-2\">\n <span aria-hidden className=\"h-2 rounded-full bg-primary\" style={{ width: `${Math.max(2, (d.readers / most) * 100)}%` }} />\n {d.readers}\n </span>\n </td>\n <td className=\"py-1.5 text-end\">{d.users}</td>\n <td className=\"py-1.5 text-end\">{formatDuration(Math.floor(d.active_ms / 1000))}</td>\n </tr>\n ))}\n </tbody>\n <tfoot>\n <tr className=\"border-t border-border font-medium\">\n <th scope=\"row\" colSpan={3} className=\"py-1.5 font-medium\">\n {t(\"stats.totalActiveTime\")}\n </th>\n <td className=\"py-1.5 text-end\" data-ckui=\"stats-active-total\">\n {formatDuration(Math.floor(totalActiveMS / 1000))}\n </td>\n </tr>\n </tfoot>\n </table>\n )}\n </section>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-top\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.top\")}</h3>\n <div className=\"flex flex-wrap gap-2\">\n <Pick label={t(\"stats.metric\")} value={metric} onChange={(v) => setMetric(v as Metric)} options={METRICS.map((x) => ({ value: x, label: t(`stats.metric_${x}`) }))} />\n <Pick label={t(\"stats.period\")} value={period} onChange={(v) => setPeriod(v as Period)} options={PERIODS.map((x) => ({ value: x, label: t(`stats.period_${x}`) }))} />\n </div>\n </div>\n {top.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : top.error ? (\n <LoadFailed error={top.error} message={t(\"stats.loadFailed\")} retry={top.reload} />\n ) : top.loading && !top.data ? (\n <Rows count={3} />\n ) : !top.data?.contents.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noTop\")}</p>\n ) : (\n <ol className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={top.loading}>\n {top.data.contents.map((c, i) => {\n const href = contentHref ? contentHref(c) : c.path;\n return (\n <li key={c.id} className=\"flex items-center gap-3 p-2.5\" data-ckui=\"stats-ranked\">\n <span className=\"w-6 text-end text-muted-foreground tabular-nums\">{i + 1}</span>\n {href ? (\n <a className=\"min-w-0 flex-1 break-words hover:underline\" href={href}>\n {c.title}\n </a>\n ) : (\n <span className=\"min-w-0 flex-1 break-words\">{c.title}</span>\n )}\n <span className=\"tabular-nums\" data-ckui=\"stats-value\">\n {c.value}\n </span>\n </li>\n );\n })}\n </ol>\n )}\n </section>\n </>\n );\n}\n\nfunction Pick({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: { value: string; label: string }[] }) {\n return (\n <Select value={value} onValueChange={(v) => onChange(String(v))}>\n <SelectTrigger size=\"sm\" aria-label={label}>\n <SelectValue>{(v: string) => options.find((o) => o.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":"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"}
|