@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.
Files changed (120) hide show
  1. package/README.md +105 -43
  2. package/dist/client/beacons.d.ts +3 -13
  3. package/dist/client/beacons.js +4 -45
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/client.d.ts +3 -0
  6. package/dist/client/client.js +45 -43
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/content/favorites.d.ts +2 -0
  9. package/dist/client/content/favorites.js.map +1 -1
  10. package/dist/client/content/types.d.ts +3 -0
  11. package/dist/client/content/types.js.map +1 -1
  12. package/dist/client/discovery.d.ts +28 -3
  13. package/dist/client/discovery.js +22 -1
  14. package/dist/client/discovery.js.map +1 -1
  15. package/dist/client/engagement.d.ts +70 -0
  16. package/dist/client/engagement.js +191 -0
  17. package/dist/client/engagement.js.map +1 -0
  18. package/dist/client/generated/routes.d.ts +35 -9
  19. package/dist/client/generated/routes.js +59 -2
  20. package/dist/client/generated/routes.js.map +1 -1
  21. package/dist/client/generated/wire.d.ts +53 -12
  22. package/dist/client/generated/wire.js.map +1 -1
  23. package/dist/client/guest-reading-history.d.ts +40 -0
  24. package/dist/client/guest-reading-history.js +144 -0
  25. package/dist/client/guest-reading-history.js.map +1 -0
  26. package/dist/client/index.d.ts +4 -2
  27. package/dist/client/stats.d.ts +5 -5
  28. package/dist/client/stats.js.map +1 -1
  29. package/dist/client.js +24 -22
  30. package/dist/components/admin/content-manager.js +30 -32
  31. package/dist/components/admin/content-manager.js.map +1 -1
  32. package/dist/components/admin/quick-search.d.ts +14 -0
  33. package/dist/components/admin/quick-search.js +60 -0
  34. package/dist/components/admin/quick-search.js.map +1 -0
  35. package/dist/components/admin/reader-content-editor.js +452 -428
  36. package/dist/components/admin/reader-content-editor.js.map +1 -1
  37. package/dist/components/admin/stats-dashboard.d.ts +4 -4
  38. package/dist/components/admin/stats-dashboard.js +132 -134
  39. package/dist/components/admin/stats-dashboard.js.map +1 -1
  40. package/dist/components/content/daily-releases.js +143 -191
  41. package/dist/components/content/daily-releases.js.map +1 -1
  42. package/dist/components/content/favorites.d.ts +6 -0
  43. package/dist/components/content/favorites.js +33 -0
  44. package/dist/components/content/favorites.js.map +1 -0
  45. package/dist/components/content/filter-dialog.js +19 -15
  46. package/dist/components/content/filter-dialog.js.map +1 -1
  47. package/dist/components/content/filters.d.ts +2 -0
  48. package/dist/components/content/filters.js +5 -1
  49. package/dist/components/content/filters.js.map +1 -1
  50. package/dist/components/content/grid.d.ts +34 -13
  51. package/dist/components/content/grid.js +300 -271
  52. package/dist/components/content/grid.js.map +1 -1
  53. package/dist/components/content/history.d.ts +6 -0
  54. package/dist/components/content/history.js +91 -0
  55. package/dist/components/content/history.js.map +1 -0
  56. package/dist/components/content/index.d.ts +1 -0
  57. package/dist/components/content/search.d.ts +11 -4
  58. package/dist/components/content/search.js +22 -11
  59. package/dist/components/content/search.js.map +1 -1
  60. package/dist/components/content/summary-tile.d.ts +14 -0
  61. package/dist/components/content/summary-tile.js +63 -0
  62. package/dist/components/content/summary-tile.js.map +1 -0
  63. package/dist/components/media-folder-editor.js +1 -1
  64. package/dist/components/media-folder-editor.js.map +1 -1
  65. package/dist/components/video-mini-player.d.ts +4 -1
  66. package/dist/components/video-mini-player.js +77 -71
  67. package/dist/components/video-mini-player.js.map +1 -1
  68. package/dist/components/video-player.d.ts +4 -1
  69. package/dist/components/video-player.js +280 -276
  70. package/dist/components/video-player.js.map +1 -1
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.js +17 -14
  73. package/dist/locales/admin/de.js +8 -8
  74. package/dist/locales/admin/de.js.map +1 -1
  75. package/dist/locales/admin/en.d.ts +4 -4
  76. package/dist/locales/admin/en.js +8 -8
  77. package/dist/locales/admin/en.js.map +1 -1
  78. package/dist/locales/admin/es.js +8 -8
  79. package/dist/locales/admin/es.js.map +1 -1
  80. package/dist/locales/admin/ja.js +8 -8
  81. package/dist/locales/admin/ja.js.map +1 -1
  82. package/dist/locales/admin/ko.js +8 -8
  83. package/dist/locales/admin/ko.js.map +1 -1
  84. package/dist/locales/admin/zh.js +8 -8
  85. package/dist/locales/admin/zh.js.map +1 -1
  86. package/dist/locales/discovery/de.js +0 -1
  87. package/dist/locales/discovery/de.js.map +1 -1
  88. package/dist/locales/discovery/en.d.ts +0 -1
  89. package/dist/locales/discovery/en.js +0 -1
  90. package/dist/locales/discovery/en.js.map +1 -1
  91. package/dist/locales/discovery/es.js +0 -1
  92. package/dist/locales/discovery/es.js.map +1 -1
  93. package/dist/locales/discovery/ja.js +0 -1
  94. package/dist/locales/discovery/ja.js.map +1 -1
  95. package/dist/locales/discovery/ko.js +0 -1
  96. package/dist/locales/discovery/ko.js.map +1 -1
  97. package/dist/locales/discovery/zh.js +0 -1
  98. package/dist/locales/discovery/zh.js.map +1 -1
  99. package/dist/locales/en.d.ts +4 -5
  100. package/dist/react/discovery.d.ts +20 -17
  101. package/dist/react/discovery.js +273 -221
  102. package/dist/react/discovery.js.map +1 -1
  103. package/dist/react/index.d.ts +2 -1
  104. package/dist/react/resources.d.ts +3 -1
  105. package/dist/react/resources.js +4 -0
  106. package/dist/react/resources.js.map +1 -1
  107. package/dist/react/stats.d.ts +2 -2
  108. package/dist/react/stats.js.map +1 -1
  109. package/dist/react/use-engagement.d.ts +26 -0
  110. package/dist/react/use-engagement.js +221 -0
  111. package/dist/react/use-engagement.js.map +1 -0
  112. package/dist/react/use-resource.d.ts +4 -3
  113. package/dist/react/use-resource.js +2 -1
  114. package/dist/react/use-resource.js.map +1 -1
  115. package/dist/react.js +12 -11
  116. package/dist/styles.css +1 -1
  117. package/dist/ui/dialog.d.ts +2 -1
  118. package/dist/ui/dialog.js +23 -20
  119. package/dist/ui/dialog.js.map +1 -1
  120. package/package.json +1 -1
@@ -1,228 +1,226 @@
1
1
  import { useMessages as e } from "../../i18n/context.js";
2
- import { Select as t, SelectContent as n, SelectItem as r, SelectTrigger as i, SelectValue as a } from "../../ui/select.js";
3
- import { AdminRoot as o, LoadFailed as s, NoAccess as c, Rows as l } from "./shell.js";
4
- import { useFlowClient as u } from "../../react/admin.js";
5
- import { useActivity as d, useTopContents as f } from "../../react/stats.js";
6
- import { useState as p } from "react";
7
- import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
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 _ = (e) => e.map((e) => typeof e == "string" ? {
10
+ var v = (e) => e.map((e) => typeof e == "string" ? {
10
11
  value: e,
11
12
  label: e
12
- } : e), v = [
13
+ } : e), y = [
13
14
  7,
14
15
  30,
15
16
  90
16
- ], y = ["views", "comments"], b = [
17
+ ], b = ["readers", "comments"], x = [
17
18
  "7d",
18
19
  "30d",
19
20
  "90d",
20
21
  "365d",
21
22
  "all"
22
- ], x = (e) => e?.code === "forbidden" || e?.code === "unauthorized";
23
- function S({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className: a, language: s, messages: c, ...l }) {
24
- let d = u({
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__ */ h(o, {
30
+ return /* @__PURE__ */ g(s, {
30
31
  flow: "stats-dashboard",
31
32
  appearance: i,
32
33
  className: a,
33
- language: s,
34
+ language: o,
34
35
  messages: c,
35
36
  guard: r,
36
- children: /* @__PURE__ */ h(C, {
37
+ children: /* @__PURE__ */ g(w, {
37
38
  ...l,
38
- client: d
39
+ client: u
39
40
  })
40
41
  });
41
42
  }
42
- function C({ kinds: t, contentHref: n, limit: r = 10, client: i }) {
43
- let { t: a } = e(), o = _(t), [u, S] = p(o[0]?.value ?? ""), [C, T] = p(7), [E, D] = p("views"), [O, k] = p("30d"), A = d(u ? {
44
- kind: u,
45
- days: C
46
- } : null, { client: i }), j = f(u ? {
47
- kind: u,
48
- metric: E,
49
- period: O,
50
- limit: r
51
- } : null, { client: i });
52
- if (x(A.error) || x(j.error)) return /* @__PURE__ */ h(c, { children: a("stats.noAccess") });
53
- let M = A.data?.days ?? [], N = Math.max(1, ...M.map((e) => e.views)), P = M.reduce((e, t) => e + t.views, 0);
54
- return /* @__PURE__ */ g(m, { children: [
55
- /* @__PURE__ */ g("div", {
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__ */ h("h2", {
58
+ children: [/* @__PURE__ */ g("h2", {
58
59
  className: "text-base font-semibold",
59
- children: a("stats.title")
60
- }), o.length > 1 && /* @__PURE__ */ h(w, {
61
- label: a("stats.kind"),
62
- value: u,
63
- onChange: S,
64
- options: o
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__ */ g("section", {
68
+ /* @__PURE__ */ _("section", {
68
69
  className: "grid gap-3",
69
70
  "data-ckui": "stats-activity",
70
- children: [/* @__PURE__ */ g("div", {
71
+ children: [/* @__PURE__ */ _("div", {
71
72
  className: "flex flex-wrap items-center justify-between gap-2",
72
- children: [/* @__PURE__ */ h("h3", {
73
+ children: [/* @__PURE__ */ g("h3", {
73
74
  className: "font-medium",
74
- children: a("stats.activity")
75
- }), /* @__PURE__ */ h(w, {
76
- label: a("stats.days"),
77
- value: String(C),
78
- onChange: (e) => T(Number(e)),
79
- options: v.map((e) => ({
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: a("stats.lastDays", { count: e })
82
+ label: o("stats.lastDays", { count: e })
82
83
  }))
83
84
  })]
84
- }), A.unavailable ? /* @__PURE__ */ h("p", {
85
+ }), j.unavailable ? /* @__PURE__ */ g("p", {
85
86
  className: "text-muted-foreground",
86
87
  role: "status",
87
- children: a("stats.unavailable")
88
- }) : A.error ? /* @__PURE__ */ h(s, {
89
- error: A.error,
90
- message: a("stats.loadFailed"),
91
- retry: A.reload
92
- }) : A.loading && !A.data ? /* @__PURE__ */ h(l, { count: 2 }) : M.length ? /* @__PURE__ */ g("table", {
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": A.loading,
95
+ "aria-busy": j.loading,
95
96
  children: [
96
- /* @__PURE__ */ h("thead", {
97
+ /* @__PURE__ */ g("thead", {
97
98
  className: "text-xs text-muted-foreground",
98
- children: /* @__PURE__ */ g("tr", { children: [
99
- /* @__PURE__ */ h("th", {
99
+ children: /* @__PURE__ */ _("tr", { children: [
100
+ /* @__PURE__ */ g("th", {
100
101
  className: "py-1 font-medium",
101
- children: a("stats.date")
102
+ children: o("stats.date")
102
103
  }),
103
- /* @__PURE__ */ h("th", {
104
+ /* @__PURE__ */ g("th", {
104
105
  className: "py-1 font-medium",
105
- children: a("stats.views")
106
+ children: o("stats.readers")
106
107
  }),
107
- /* @__PURE__ */ h("th", {
108
+ /* @__PURE__ */ g("th", {
108
109
  className: "py-1 text-end font-medium",
109
- children: a("stats.viewers")
110
+ children: o("stats.users")
110
111
  }),
111
- /* @__PURE__ */ h("th", {
112
+ /* @__PURE__ */ g("th", {
112
113
  className: "py-1 text-end font-medium",
113
- children: a("stats.users")
114
+ children: o("stats.activeTime")
114
115
  })
115
116
  ] })
116
117
  }),
117
- /* @__PURE__ */ h("tbody", { children: M.map((e) => /* @__PURE__ */ g("tr", {
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__ */ h("td", {
122
+ /* @__PURE__ */ g("td", {
122
123
  className: "py-1.5 pe-3 whitespace-nowrap",
123
124
  children: e.date
124
125
  }),
125
- /* @__PURE__ */ h("td", {
126
+ /* @__PURE__ */ g("td", {
126
127
  className: "w-1/2 py-1.5 pe-3",
127
- children: /* @__PURE__ */ g("span", {
128
+ children: /* @__PURE__ */ _("span", {
128
129
  className: "flex items-center gap-2",
129
- children: [/* @__PURE__ */ h("span", {
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.views / N * 100)}%` }
133
- }), e.views]
133
+ style: { width: `${Math.max(2, e.readers / P * 100)}%` }
134
+ }), e.readers]
134
135
  })
135
136
  }),
136
- /* @__PURE__ */ h("td", {
137
+ /* @__PURE__ */ g("td", {
137
138
  className: "py-1.5 text-end",
138
- children: e.viewers
139
+ children: e.users
139
140
  }),
140
- /* @__PURE__ */ h("td", {
141
+ /* @__PURE__ */ g("td", {
141
142
  className: "py-1.5 text-end",
142
- children: e.users
143
+ children: t(Math.floor(e.active_ms / 1e3))
143
144
  })
144
145
  ]
145
146
  }, e.date)) }),
146
- /* @__PURE__ */ h("tfoot", { children: /* @__PURE__ */ g("tr", {
147
+ /* @__PURE__ */ g("tfoot", { children: /* @__PURE__ */ _("tr", {
147
148
  className: "border-t border-border font-medium",
148
- children: [
149
- /* @__PURE__ */ h("td", {
150
- className: "py-1.5",
151
- children: a("stats.total")
152
- }),
153
- /* @__PURE__ */ h("td", {
154
- className: "py-1.5",
155
- "data-ckui": "stats-total",
156
- children: P
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__ */ h("p", {
161
+ }) : /* @__PURE__ */ g("p", {
164
162
  className: "text-muted-foreground",
165
- children: a("stats.noActivity")
163
+ children: o("stats.noActivity")
166
164
  })]
167
165
  }),
168
- /* @__PURE__ */ g("section", {
166
+ /* @__PURE__ */ _("section", {
169
167
  className: "grid gap-3",
170
168
  "data-ckui": "stats-top",
171
- children: [/* @__PURE__ */ g("div", {
169
+ children: [/* @__PURE__ */ _("div", {
172
170
  className: "flex flex-wrap items-center justify-between gap-2",
173
- children: [/* @__PURE__ */ h("h3", {
171
+ children: [/* @__PURE__ */ g("h3", {
174
172
  className: "font-medium",
175
- children: a("stats.top")
176
- }), /* @__PURE__ */ g("div", {
173
+ children: o("stats.top")
174
+ }), /* @__PURE__ */ _("div", {
177
175
  className: "flex flex-wrap gap-2",
178
- children: [/* @__PURE__ */ h(w, {
179
- label: a("stats.metric"),
180
- value: E,
181
- onChange: (e) => D(e),
182
- options: y.map((e) => ({
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: a(`stats.metric_${e}`)
182
+ label: o(`stats.metric_${e}`)
185
183
  }))
186
- }), /* @__PURE__ */ h(w, {
187
- label: a("stats.period"),
188
- value: O,
189
- onChange: (e) => k(e),
190
- options: b.map((e) => ({
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: a(`stats.period_${e}`)
190
+ label: o(`stats.period_${e}`)
193
191
  }))
194
192
  })]
195
193
  })]
196
- }), j.unavailable ? /* @__PURE__ */ h("p", {
194
+ }), M.unavailable ? /* @__PURE__ */ g("p", {
197
195
  className: "text-muted-foreground",
198
196
  role: "status",
199
- children: a("stats.unavailable")
200
- }) : j.error ? /* @__PURE__ */ h(s, {
201
- error: j.error,
202
- message: a("stats.loadFailed"),
203
- retry: j.reload
204
- }) : j.loading && !j.data ? /* @__PURE__ */ h(l, { count: 3 }) : j.data?.contents.length ? /* @__PURE__ */ h("ol", {
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": j.loading,
207
- children: j.data.contents.map((e, t) => {
208
- let r = n ? n(e) : e.path;
209
- return /* @__PURE__ */ g("li", {
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__ */ h("span", {
211
+ /* @__PURE__ */ g("span", {
214
212
  className: "w-6 text-end text-muted-foreground tabular-nums",
215
213
  children: t + 1
216
214
  }),
217
- r ? /* @__PURE__ */ h("a", {
215
+ n ? /* @__PURE__ */ g("a", {
218
216
  className: "min-w-0 flex-1 break-words hover:underline",
219
- href: r,
217
+ href: n,
220
218
  children: e.title
221
- }) : /* @__PURE__ */ h("span", {
219
+ }) : /* @__PURE__ */ g("span", {
222
220
  className: "min-w-0 flex-1 break-words",
223
221
  children: e.title
224
222
  }),
225
- /* @__PURE__ */ h("span", {
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__ */ h("p", {
231
+ }) : /* @__PURE__ */ g("p", {
234
232
  className: "text-muted-foreground",
235
- children: a("stats.noTop")
233
+ children: o("stats.noTop")
236
234
  })]
237
235
  })
238
236
  ] });
239
237
  }
240
- function w({ label: e, value: o, onChange: s, options: c }) {
241
- return /* @__PURE__ */ g(t, {
242
- value: o,
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__ */ h(i, {
242
+ children: [/* @__PURE__ */ g(a, {
245
243
  size: "sm",
246
244
  "aria-label": e,
247
- children: /* @__PURE__ */ h(a, { children: (e) => c.find((t) => t.value === e)?.label ?? e })
248
- }), /* @__PURE__ */ h(n, { children: c.map((e) => /* @__PURE__ */ h(r, {
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 { S as StatsDashboard };
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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"}