@burdenoff/microfe-assethandler 2026.826.4 → 2026.826.5
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnowledgeBasePage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/KnowledgeBasePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KnowledgeBasePage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/KnowledgeBasePage.tsx"],"names":[],"mappings":"AA0jBA,wBAAgB,iBAAiB,gCA0VhC"}
|
|
@@ -9,15 +9,15 @@ import { Pagination as s, usePagination as c } from "../components/Pagination.js
|
|
|
9
9
|
import { ChartCard as l } from "../components/ChartCard.js";
|
|
10
10
|
import { SectionHeader as u } from "../components/SectionHeader.js";
|
|
11
11
|
import "../components/index.js";
|
|
12
|
-
import { formatAssetOption as d, formatIncidentOption as f, parseTagsInput as
|
|
13
|
-
import { useMemo as
|
|
14
|
-
import { BookOpen as
|
|
15
|
-
import { gql as
|
|
16
|
-
import { useMutation as
|
|
17
|
-
import { Fragment as
|
|
18
|
-
import { Button as
|
|
12
|
+
import { formatAssetOption as d, formatIncidentOption as f, parseTagsInput as p } from "../utils/knowledgeBase.js";
|
|
13
|
+
import { useMemo as m, useState as h } from "react";
|
|
14
|
+
import { BookOpen as g, ExternalLink as _, Eye as v, Link2 as y, Pencil as b, Plus as x, Search as S, User as C, X as w } from "lucide-react";
|
|
15
|
+
import { gql as T } from "@apollo/client";
|
|
16
|
+
import { useMutation as E, useQuery as D } from "@apollo/client/react";
|
|
17
|
+
import { Fragment as O, jsx as k, jsxs as A } from "react/jsx-runtime";
|
|
18
|
+
import { Button as j, GlassCard as ee, IllustratedEmptyState as M, Input as N, Label as P, PagePurpose as F, ResponsiveDialog as I, ResponsiveDialogContent as L, ResponsiveDialogDescription as R, ResponsiveDialogFooter as z, ResponsiveDialogHeader as B, ResponsiveDialogTitle as V, Select as H, SelectContent as U, SelectItem as W, SelectTrigger as G, SelectValue as K, Textarea as te } from "@burdenoff/fe-libs/ui";
|
|
19
19
|
//#region src/assethandler/pages/KnowledgeBasePage.tsx
|
|
20
|
-
var ne =
|
|
20
|
+
var ne = T`
|
|
21
21
|
query AssetHandlerKnowledgeArticles {
|
|
22
22
|
knowledgeArticles {
|
|
23
23
|
id
|
|
@@ -37,25 +37,32 @@ var ne = w`
|
|
|
37
37
|
relatedIncidentId
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
`, q =
|
|
40
|
+
`, q = T`
|
|
41
41
|
mutation AssetHandlerCreateKnowledgeArticle($input: CreateKnowledgeArticleInput!) {
|
|
42
42
|
createKnowledgeArticle(input: $input) {
|
|
43
43
|
id
|
|
44
44
|
title
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
`,
|
|
47
|
+
`, re = T`
|
|
48
48
|
mutation AssetHandlerUpdateKnowledgeArticle($id: ID!, $input: UpdateKnowledgeArticleInput!) {
|
|
49
49
|
updateKnowledgeArticle(id: $id, input: $input) {
|
|
50
50
|
id
|
|
51
51
|
title
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
`,
|
|
54
|
+
`, ie = T`
|
|
55
|
+
mutation AssetHandlerRecordKnowledgeArticleView($id: ID!) {
|
|
56
|
+
recordKnowledgeArticleView(id: $id) {
|
|
57
|
+
id
|
|
58
|
+
views
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`, ae = [
|
|
55
62
|
"DRAFT",
|
|
56
63
|
"PUBLISHED",
|
|
57
64
|
"ARCHIVED"
|
|
58
|
-
],
|
|
65
|
+
], J = {
|
|
59
66
|
PUBLISHED: {
|
|
60
67
|
label: "Published",
|
|
61
68
|
className: "bg-status-success-bg-subtle text-status-success-text"
|
|
@@ -68,36 +75,36 @@ var ne = w`
|
|
|
68
75
|
label: "Archived",
|
|
69
76
|
className: "bg-status-warning-bg-subtle text-status-warning-text"
|
|
70
77
|
}
|
|
71
|
-
},
|
|
78
|
+
}, Y = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), X = (e) => e ? new Date(e).toLocaleDateString(void 0, {
|
|
72
79
|
year: "numeric",
|
|
73
80
|
month: "short",
|
|
74
81
|
day: "numeric"
|
|
75
82
|
}) : "—";
|
|
76
|
-
function
|
|
83
|
+
function Z(e) {
|
|
77
84
|
if (e.summary && e.summary.trim()) return e.summary.trim();
|
|
78
85
|
let t = e.content.replace(/\s+/g, " ").trim();
|
|
79
86
|
return t.length > 160 ? `${t.slice(0, 160)}…` : t;
|
|
80
87
|
}
|
|
81
|
-
function
|
|
88
|
+
function oe(e) {
|
|
82
89
|
return (e.replace(/[^a-zA-Z0-9]/g, "").slice(0, 2) || "?").toUpperCase();
|
|
83
90
|
}
|
|
84
|
-
function
|
|
85
|
-
let t =
|
|
91
|
+
function Q({ status: e }) {
|
|
92
|
+
let t = J[e] ?? {
|
|
86
93
|
label: e,
|
|
87
94
|
className: "bg-bg-sunken text-text-secondary"
|
|
88
95
|
};
|
|
89
|
-
return /* @__PURE__ */
|
|
96
|
+
return /* @__PURE__ */ k("span", {
|
|
90
97
|
className: r("inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium", t.className),
|
|
91
98
|
children: t.label
|
|
92
99
|
});
|
|
93
100
|
}
|
|
94
|
-
function
|
|
95
|
-
return /* @__PURE__ */
|
|
101
|
+
function se({ open: e, onOpenChange: t, article: n, onSaved: r }) {
|
|
102
|
+
return /* @__PURE__ */ k(I, {
|
|
96
103
|
open: e,
|
|
97
104
|
onOpenChange: t,
|
|
98
|
-
children: /* @__PURE__ */
|
|
105
|
+
children: /* @__PURE__ */ k(L, {
|
|
99
106
|
className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl",
|
|
100
|
-
children: e && /* @__PURE__ */
|
|
107
|
+
children: e && /* @__PURE__ */ k(ce, {
|
|
101
108
|
article: n,
|
|
102
109
|
onOpenChange: t,
|
|
103
110
|
onSaved: r
|
|
@@ -105,141 +112,141 @@ function ae({ open: e, onOpenChange: t, article: n, onSaved: r }) {
|
|
|
105
112
|
})
|
|
106
113
|
});
|
|
107
114
|
}
|
|
108
|
-
function
|
|
109
|
-
let a = !!t, { assets: o, incidents: s } = e(), [c, l] =
|
|
110
|
-
l(""),
|
|
111
|
-
}, [
|
|
115
|
+
function ce({ article: t, onOpenChange: n, onSaved: r }) {
|
|
116
|
+
let a = !!t, { assets: o, incidents: s } = e(), [c, l] = h(t?.title ?? ""), [u, g] = h(t?.slug ?? ""), [_, v] = h(a), [y, b] = h(t?.summary ?? ""), [x, S] = h(t?.content ?? ""), [C, T] = h(t ? t.tags.join(", ") : ""), [D, ee] = h(t?.status ?? "PUBLISHED"), [M, F] = h(t?.relatedAssetId ?? ""), [I, L] = h(t?.relatedIncidentId ?? ""), ne = () => {
|
|
117
|
+
l(""), g(""), v(!1), b(""), S(""), T(""), ee("PUBLISHED"), F(""), L("");
|
|
118
|
+
}, [ie, { loading: J }] = E(q, {
|
|
112
119
|
onCompleted: (e) => {
|
|
113
120
|
i.success("Article published", `${e.createKnowledgeArticle.title} is now available.`), ne(), n(!1), r();
|
|
114
121
|
},
|
|
115
122
|
onError: (e) => i.error("Could not publish article", e.message)
|
|
116
|
-
}), [
|
|
123
|
+
}), [X, { loading: Z }] = E(re, {
|
|
117
124
|
refetchQueries: ["AssetHandlerKnowledgeArticles"],
|
|
118
125
|
onCompleted: (e) => {
|
|
119
126
|
i.success("Article updated", `${e.updateKnowledgeArticle.title} has been saved.`), n(!1), r();
|
|
120
127
|
},
|
|
121
128
|
onError: (e) => i.error("Could not update article", e.message)
|
|
122
|
-
}),
|
|
123
|
-
l(e),
|
|
129
|
+
}), oe = m(() => o.find((e) => e.id === M), [o, M]), Q = m(() => s.find((e) => e.id === I), [s, I]), se = (e) => {
|
|
130
|
+
l(e), _ || g(Y(e));
|
|
124
131
|
};
|
|
125
|
-
return /* @__PURE__ */
|
|
126
|
-
/* @__PURE__ */
|
|
127
|
-
/* @__PURE__ */
|
|
132
|
+
return /* @__PURE__ */ A(O, { children: [
|
|
133
|
+
/* @__PURE__ */ A(B, { children: [/* @__PURE__ */ k(V, { children: a ? "Edit article" : "New Article" }), /* @__PURE__ */ k(R, { children: a ? "Update the runbook or fix so the team has the latest guidance." : "Capture a runbook or fix so your team can find it later." })] }),
|
|
134
|
+
/* @__PURE__ */ A("div", {
|
|
128
135
|
className: "space-y-4",
|
|
129
136
|
children: [
|
|
130
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ A("div", {
|
|
131
138
|
className: "grid gap-4 md:grid-cols-2",
|
|
132
|
-
children: [/* @__PURE__ */
|
|
139
|
+
children: [/* @__PURE__ */ A("div", {
|
|
133
140
|
className: "space-y-2",
|
|
134
|
-
children: [/* @__PURE__ */
|
|
141
|
+
children: [/* @__PURE__ */ k(P, { children: "Title *" }), /* @__PURE__ */ k(N, {
|
|
135
142
|
value: c,
|
|
136
|
-
onChange: (e) =>
|
|
143
|
+
onChange: (e) => se(e.target.value)
|
|
137
144
|
})]
|
|
138
|
-
}), /* @__PURE__ */
|
|
145
|
+
}), /* @__PURE__ */ A("div", {
|
|
139
146
|
className: "space-y-2",
|
|
140
|
-
children: [/* @__PURE__ */
|
|
147
|
+
children: [/* @__PURE__ */ k(P, { children: "Slug *" }), /* @__PURE__ */ k(N, {
|
|
141
148
|
value: u,
|
|
142
149
|
disabled: a,
|
|
143
150
|
onChange: (e) => {
|
|
144
|
-
|
|
151
|
+
v(!0), g(e.target.value);
|
|
145
152
|
}
|
|
146
153
|
})]
|
|
147
154
|
})]
|
|
148
155
|
}),
|
|
149
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ A("div", {
|
|
150
157
|
className: "space-y-2",
|
|
151
|
-
children: [/* @__PURE__ */
|
|
158
|
+
children: [/* @__PURE__ */ k(P, { children: "Summary" }), /* @__PURE__ */ k(N, {
|
|
152
159
|
placeholder: "One-line description",
|
|
153
|
-
value:
|
|
154
|
-
onChange: (e) =>
|
|
160
|
+
value: y,
|
|
161
|
+
onChange: (e) => b(e.target.value)
|
|
155
162
|
})]
|
|
156
163
|
}),
|
|
157
|
-
/* @__PURE__ */
|
|
164
|
+
/* @__PURE__ */ A("div", {
|
|
158
165
|
className: "space-y-2",
|
|
159
|
-
children: [/* @__PURE__ */
|
|
166
|
+
children: [/* @__PURE__ */ k(P, { children: "Body *" }), /* @__PURE__ */ k(te, {
|
|
160
167
|
placeholder: "Write the full article body — steps, context, links…",
|
|
161
|
-
value:
|
|
162
|
-
onChange: (e) =>
|
|
168
|
+
value: x,
|
|
169
|
+
onChange: (e) => S(e.target.value),
|
|
163
170
|
rows: 8
|
|
164
171
|
})]
|
|
165
172
|
}),
|
|
166
|
-
/* @__PURE__ */
|
|
173
|
+
/* @__PURE__ */ A("div", {
|
|
167
174
|
className: "grid gap-4 md:grid-cols-2",
|
|
168
|
-
children: [/* @__PURE__ */
|
|
175
|
+
children: [/* @__PURE__ */ A("div", {
|
|
169
176
|
className: "space-y-2",
|
|
170
|
-
children: [/* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ k(P, { children: "Category tags" }), /* @__PURE__ */ k(N, {
|
|
171
178
|
placeholder: "Runbooks, Fixes, Procedures…",
|
|
172
|
-
value:
|
|
173
|
-
onChange: (e) =>
|
|
179
|
+
value: C,
|
|
180
|
+
onChange: (e) => T(e.target.value)
|
|
174
181
|
})]
|
|
175
|
-
}), /* @__PURE__ */
|
|
182
|
+
}), /* @__PURE__ */ A("div", {
|
|
176
183
|
className: "space-y-2",
|
|
177
|
-
children: [/* @__PURE__ */
|
|
178
|
-
value:
|
|
179
|
-
onValueChange:
|
|
180
|
-
children: [/* @__PURE__ */
|
|
184
|
+
children: [/* @__PURE__ */ k(P, { children: "Status" }), /* @__PURE__ */ A(H, {
|
|
185
|
+
value: D,
|
|
186
|
+
onValueChange: ee,
|
|
187
|
+
children: [/* @__PURE__ */ k(G, { children: /* @__PURE__ */ k(K, {}) }), /* @__PURE__ */ k(U, { children: ae.map((e) => /* @__PURE__ */ k(W, {
|
|
181
188
|
value: e,
|
|
182
189
|
children: e
|
|
183
190
|
}, e)) })]
|
|
184
191
|
})]
|
|
185
192
|
})]
|
|
186
193
|
}),
|
|
187
|
-
/* @__PURE__ */
|
|
194
|
+
/* @__PURE__ */ A("div", {
|
|
188
195
|
className: "grid gap-4 md:grid-cols-2",
|
|
189
|
-
children: [/* @__PURE__ */
|
|
196
|
+
children: [/* @__PURE__ */ A("div", {
|
|
190
197
|
className: "space-y-2",
|
|
191
198
|
children: [
|
|
192
|
-
/* @__PURE__ */
|
|
193
|
-
/* @__PURE__ */
|
|
199
|
+
/* @__PURE__ */ k(P, { children: "Related asset" }),
|
|
200
|
+
/* @__PURE__ */ A(H, {
|
|
194
201
|
value: M,
|
|
195
202
|
onValueChange: F,
|
|
196
|
-
children: [/* @__PURE__ */
|
|
203
|
+
children: [/* @__PURE__ */ k(G, { children: /* @__PURE__ */ k(K, { placeholder: "Select an asset" }) }), /* @__PURE__ */ A(U, { children: [/* @__PURE__ */ k(W, {
|
|
197
204
|
value: "",
|
|
198
205
|
children: "None"
|
|
199
|
-
}), o.map((e) => /* @__PURE__ */
|
|
206
|
+
}), o.map((e) => /* @__PURE__ */ k(W, {
|
|
200
207
|
value: e.id,
|
|
201
208
|
children: d(e)
|
|
202
209
|
}, e.id))] })]
|
|
203
210
|
}),
|
|
204
|
-
|
|
211
|
+
oe ? /* @__PURE__ */ k("div", {
|
|
205
212
|
className: "flex flex-wrap gap-2",
|
|
206
|
-
children: /* @__PURE__ */
|
|
213
|
+
children: /* @__PURE__ */ A("span", {
|
|
207
214
|
className: "inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary",
|
|
208
|
-
children: [d(
|
|
215
|
+
children: [d(oe), /* @__PURE__ */ k("button", {
|
|
209
216
|
type: "button",
|
|
210
217
|
onClick: () => F(""),
|
|
211
218
|
className: "rounded-full p-0.5 hover:bg-action-primary-bg/20",
|
|
212
219
|
"aria-label": "Clear related asset",
|
|
213
|
-
children: /* @__PURE__ */
|
|
220
|
+
children: /* @__PURE__ */ k(w, { className: "h-3 w-3" })
|
|
214
221
|
})]
|
|
215
222
|
})
|
|
216
223
|
}) : null
|
|
217
224
|
]
|
|
218
|
-
}), /* @__PURE__ */
|
|
225
|
+
}), /* @__PURE__ */ A("div", {
|
|
219
226
|
className: "space-y-2",
|
|
220
227
|
children: [
|
|
221
|
-
/* @__PURE__ */
|
|
222
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ k(P, { children: "Related incident" }),
|
|
229
|
+
/* @__PURE__ */ A(H, {
|
|
223
230
|
value: I,
|
|
224
231
|
onValueChange: L,
|
|
225
|
-
children: [/* @__PURE__ */
|
|
232
|
+
children: [/* @__PURE__ */ k(G, { children: /* @__PURE__ */ k(K, { placeholder: "Select an incident" }) }), /* @__PURE__ */ A(U, { children: [/* @__PURE__ */ k(W, {
|
|
226
233
|
value: "",
|
|
227
234
|
children: "None"
|
|
228
|
-
}), s.map((e) => /* @__PURE__ */
|
|
235
|
+
}), s.map((e) => /* @__PURE__ */ k(W, {
|
|
229
236
|
value: e.id,
|
|
230
237
|
children: f(e)
|
|
231
238
|
}, e.id))] })]
|
|
232
239
|
}),
|
|
233
|
-
|
|
240
|
+
Q ? /* @__PURE__ */ k("div", {
|
|
234
241
|
className: "flex flex-wrap gap-2",
|
|
235
|
-
children: /* @__PURE__ */
|
|
242
|
+
children: /* @__PURE__ */ A("span", {
|
|
236
243
|
className: "inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary",
|
|
237
|
-
children: [f(
|
|
244
|
+
children: [f(Q), /* @__PURE__ */ k("button", {
|
|
238
245
|
type: "button",
|
|
239
246
|
onClick: () => L(""),
|
|
240
247
|
className: "rounded-full p-0.5 hover:bg-action-primary-bg/20",
|
|
241
248
|
"aria-label": "Clear related incident",
|
|
242
|
-
children: /* @__PURE__ */
|
|
249
|
+
children: /* @__PURE__ */ k(w, { className: "h-3 w-3" })
|
|
243
250
|
})]
|
|
244
251
|
})
|
|
245
252
|
}) : null
|
|
@@ -248,105 +255,105 @@ function oe({ article: t, onOpenChange: n, onSaved: r }) {
|
|
|
248
255
|
})
|
|
249
256
|
]
|
|
250
257
|
}),
|
|
251
|
-
/* @__PURE__ */
|
|
258
|
+
/* @__PURE__ */ A(z, { children: [/* @__PURE__ */ k(j, {
|
|
252
259
|
variant: "outline",
|
|
253
260
|
onClick: () => n(!1),
|
|
254
|
-
disabled:
|
|
261
|
+
disabled: J || Z,
|
|
255
262
|
children: "Cancel"
|
|
256
|
-
}), /* @__PURE__ */
|
|
263
|
+
}), /* @__PURE__ */ k(j, {
|
|
257
264
|
onClick: () => {
|
|
258
265
|
if (!c.trim() || !u.trim()) {
|
|
259
266
|
i.warning("Missing details", "Title and slug are required.");
|
|
260
267
|
return;
|
|
261
268
|
}
|
|
262
|
-
if (!
|
|
269
|
+
if (!x.trim()) {
|
|
263
270
|
i.warning("Body required", "Write the article body before publishing.");
|
|
264
271
|
return;
|
|
265
272
|
}
|
|
266
|
-
let e =
|
|
273
|
+
let e = p(C), n = {
|
|
267
274
|
title: c.trim(),
|
|
268
|
-
summary:
|
|
269
|
-
content:
|
|
275
|
+
summary: y.trim() || null,
|
|
276
|
+
content: x.trim(),
|
|
270
277
|
tags: e,
|
|
271
|
-
status:
|
|
278
|
+
status: D,
|
|
272
279
|
relatedAssetId: M || null,
|
|
273
280
|
relatedIncidentId: I || null
|
|
274
281
|
};
|
|
275
|
-
a && t ?
|
|
282
|
+
a && t ? X({ variables: {
|
|
276
283
|
id: t.id,
|
|
277
284
|
input: n
|
|
278
|
-
} }) :
|
|
285
|
+
} }) : ie({ variables: { input: {
|
|
279
286
|
...n,
|
|
280
287
|
slug: u.trim()
|
|
281
288
|
} } });
|
|
282
289
|
},
|
|
283
|
-
disabled:
|
|
284
|
-
children: a ?
|
|
290
|
+
disabled: J || Z,
|
|
291
|
+
children: a ? Z ? "Saving…" : "Save changes" : J ? "Publishing…" : "Publish article"
|
|
285
292
|
})] })
|
|
286
293
|
] });
|
|
287
294
|
}
|
|
288
|
-
function
|
|
295
|
+
function le({ article: e, onOpenChange: t, onEdit: r }) {
|
|
289
296
|
let i = n();
|
|
290
|
-
return /* @__PURE__ */
|
|
297
|
+
return /* @__PURE__ */ k(I, {
|
|
291
298
|
open: !!e,
|
|
292
299
|
onOpenChange: t,
|
|
293
|
-
children: /* @__PURE__ */
|
|
300
|
+
children: /* @__PURE__ */ k(L, {
|
|
294
301
|
className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl",
|
|
295
|
-
children: e ? /* @__PURE__ */
|
|
296
|
-
/* @__PURE__ */
|
|
302
|
+
children: e ? /* @__PURE__ */ A(O, { children: [
|
|
303
|
+
/* @__PURE__ */ A(B, { children: [/* @__PURE__ */ A("div", {
|
|
297
304
|
className: "flex flex-wrap items-center gap-2",
|
|
298
|
-
children: [/* @__PURE__ */
|
|
305
|
+
children: [/* @__PURE__ */ k(Q, { status: e.status }), e.tags.map((e) => /* @__PURE__ */ k("span", {
|
|
299
306
|
className: "rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary",
|
|
300
307
|
children: e
|
|
301
308
|
}, e))]
|
|
302
|
-
}), /* @__PURE__ */
|
|
309
|
+
}), /* @__PURE__ */ A("div", {
|
|
303
310
|
className: "flex items-start justify-between gap-4",
|
|
304
|
-
children: [/* @__PURE__ */
|
|
311
|
+
children: [/* @__PURE__ */ A("div", {
|
|
305
312
|
className: "min-w-0 flex-1",
|
|
306
|
-
children: [/* @__PURE__ */
|
|
313
|
+
children: [/* @__PURE__ */ k(V, {
|
|
307
314
|
className: "mt-1",
|
|
308
315
|
children: e.title
|
|
309
|
-
}), /* @__PURE__ */
|
|
316
|
+
}), /* @__PURE__ */ A(R, { children: [
|
|
310
317
|
"Updated ",
|
|
311
|
-
|
|
318
|
+
X(e.updatedAt),
|
|
312
319
|
" · ",
|
|
313
320
|
e.views.toLocaleString(),
|
|
314
321
|
" views"
|
|
315
322
|
] })]
|
|
316
|
-
}), /* @__PURE__ */
|
|
323
|
+
}), /* @__PURE__ */ A(j, {
|
|
317
324
|
variant: "outline",
|
|
318
325
|
size: "sm",
|
|
319
326
|
onClick: () => {
|
|
320
327
|
t(!1), r(e);
|
|
321
328
|
},
|
|
322
|
-
children: [/* @__PURE__ */
|
|
329
|
+
children: [/* @__PURE__ */ k(b, { className: "mr-1.5 h-3.5 w-3.5" }), "Edit"]
|
|
323
330
|
})]
|
|
324
331
|
})] }),
|
|
325
|
-
/* @__PURE__ */
|
|
332
|
+
/* @__PURE__ */ k("div", {
|
|
326
333
|
className: "whitespace-pre-wrap text-sm leading-relaxed text-text-primary",
|
|
327
334
|
children: e.content
|
|
328
335
|
}),
|
|
329
|
-
e.relatedAssetId || e.relatedIncidentId ? /* @__PURE__ */
|
|
336
|
+
e.relatedAssetId || e.relatedIncidentId ? /* @__PURE__ */ A("div", {
|
|
330
337
|
className: "mt-4 space-y-2 border-t border-border-seam pt-4",
|
|
331
|
-
children: [/* @__PURE__ */
|
|
338
|
+
children: [/* @__PURE__ */ A("h4", {
|
|
332
339
|
className: "flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-text-secondary",
|
|
333
|
-
children: [/* @__PURE__ */
|
|
334
|
-
}), /* @__PURE__ */
|
|
340
|
+
children: [/* @__PURE__ */ k(y, { className: "h-3.5 w-3.5" }), "Linked records"]
|
|
341
|
+
}), /* @__PURE__ */ A("div", {
|
|
335
342
|
className: "flex flex-wrap gap-2",
|
|
336
|
-
children: [e.relatedAssetId ? /* @__PURE__ */
|
|
343
|
+
children: [e.relatedAssetId ? /* @__PURE__ */ A(j, {
|
|
337
344
|
variant: "outline",
|
|
338
345
|
size: "sm",
|
|
339
346
|
onClick: () => {
|
|
340
347
|
t(!1), i(`/assets/${e.relatedAssetId}`);
|
|
341
348
|
},
|
|
342
|
-
children: ["View linked asset", /* @__PURE__ */
|
|
343
|
-
}) : null, e.relatedIncidentId ? /* @__PURE__ */
|
|
349
|
+
children: ["View linked asset", /* @__PURE__ */ k(_, { className: "ml-1.5 h-3.5 w-3.5" })]
|
|
350
|
+
}) : null, e.relatedIncidentId ? /* @__PURE__ */ A(j, {
|
|
344
351
|
variant: "outline",
|
|
345
352
|
size: "sm",
|
|
346
353
|
onClick: () => {
|
|
347
354
|
t(!1), i(`/itil/incidents/${e.relatedIncidentId}`);
|
|
348
355
|
},
|
|
349
|
-
children: ["View linked incident", /* @__PURE__ */
|
|
356
|
+
children: ["View linked incident", /* @__PURE__ */ k(_, { className: "ml-1.5 h-3.5 w-3.5" })]
|
|
350
357
|
}) : null]
|
|
351
358
|
})]
|
|
352
359
|
}) : null
|
|
@@ -354,96 +361,106 @@ function se({ article: e, onOpenChange: t, onEdit: r }) {
|
|
|
354
361
|
})
|
|
355
362
|
});
|
|
356
363
|
}
|
|
357
|
-
function
|
|
358
|
-
let [e, n] =
|
|
364
|
+
function ue() {
|
|
365
|
+
let [e, n] = h(!1), [d, f] = h(null), [p, _] = h(""), [y, b] = h("ALL"), [w, T] = h("ALL"), [O, I] = h("recent"), [L, R] = h(null), { data: z, loading: B, refetch: V } = D(ne, { fetchPolicy: "cache-and-network" }), [te] = E(ie), q = m(() => z?.knowledgeArticles ?? [], [z?.knowledgeArticles]), re = m(() => {
|
|
359
366
|
let e = /* @__PURE__ */ new Map();
|
|
360
|
-
for (let t of
|
|
367
|
+
for (let t of q) for (let n of t.tags) e.set(n, (e.get(n) ?? 0) + 1);
|
|
361
368
|
return [...e.entries()].sort((e, t) => t[1] - e[1]);
|
|
362
|
-
}, [
|
|
363
|
-
let e =
|
|
364
|
-
return [...
|
|
369
|
+
}, [q]), Y = m(() => {
|
|
370
|
+
let e = p.trim().toLowerCase();
|
|
371
|
+
return [...q.filter((t) => y !== "ALL" && !t.tags.includes(y) || w !== "ALL" && t.status !== w ? !1 : e ? [
|
|
365
372
|
t.title,
|
|
366
373
|
t.summary ?? "",
|
|
367
374
|
t.content,
|
|
368
375
|
t.tags.join(" ")
|
|
369
|
-
].join(" ").toLowerCase().includes(e) : !0)].sort((e, t) =>
|
|
376
|
+
].join(" ").toLowerCase().includes(e) : !0)].sort((e, t) => O === "views" ? t.views - e.views : new Date(t.updatedAt).getTime() - new Date(e.updatedAt).getTime());
|
|
370
377
|
}, [
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
g,
|
|
378
|
+
q,
|
|
379
|
+
p,
|
|
374
380
|
y,
|
|
375
|
-
w
|
|
376
|
-
|
|
377
|
-
|
|
381
|
+
w,
|
|
382
|
+
O
|
|
383
|
+
]), ce = m(() => {
|
|
384
|
+
let e = q.reduce((e, t) => (e[t.status] = (e[t.status] ?? 0) + 1, e), {});
|
|
378
385
|
return Object.entries(e).map(([e, n], r) => ({
|
|
379
|
-
name:
|
|
386
|
+
name: J[e]?.label ?? e,
|
|
380
387
|
value: n,
|
|
381
388
|
color: t(r)
|
|
382
389
|
}));
|
|
383
|
-
}, [
|
|
390
|
+
}, [q]), ue = m(() => [...q].filter((e) => e.views > 0).sort((e, t) => t.views - e.views).slice(0, 6).map((e) => ({
|
|
384
391
|
name: e.title,
|
|
385
392
|
views: e.views
|
|
386
|
-
})), [
|
|
393
|
+
})), [q]), $ = c(Y, {
|
|
387
394
|
pageSize: 9,
|
|
388
|
-
resetKey: `${
|
|
389
|
-
})
|
|
390
|
-
|
|
395
|
+
resetKey: `${p}|${y}|${w}|${O}`
|
|
396
|
+
}), de = () => {
|
|
397
|
+
f(null), n(!0);
|
|
398
|
+
}, fe = (e) => {
|
|
399
|
+
R(null), f(e), n(!0);
|
|
400
|
+
}, pe = (e) => {
|
|
401
|
+
R(e), te({ variables: { id: e.id } }).then((e) => {
|
|
402
|
+
let t = e.data?.recordKnowledgeArticleView;
|
|
403
|
+
t && (R((e) => e?.id === t.id ? {
|
|
404
|
+
...e,
|
|
405
|
+
views: t.views
|
|
406
|
+
} : e), V());
|
|
407
|
+
}).catch((e) => {
|
|
408
|
+
i.error("Could not record article view", e instanceof Error ? e.message : "The article is still available to read.");
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
return /* @__PURE__ */ A("div", {
|
|
391
412
|
className: "space-y-5 p-4 sm:p-6",
|
|
392
413
|
children: [
|
|
393
|
-
/* @__PURE__ */
|
|
414
|
+
/* @__PURE__ */ k(u, {
|
|
394
415
|
hero: !0,
|
|
395
|
-
icon:
|
|
416
|
+
icon: g,
|
|
396
417
|
title: "Knowledge Base",
|
|
397
418
|
description: "Turn one-off fixes and runbooks into repeatable, searchable operational knowledge.",
|
|
398
|
-
actions: /* @__PURE__ */
|
|
419
|
+
actions: /* @__PURE__ */ A(j, {
|
|
399
420
|
className: "w-full sm:w-auto",
|
|
400
|
-
onClick:
|
|
401
|
-
|
|
402
|
-
},
|
|
403
|
-
children: [/* @__PURE__ */ O(b, { className: "mr-2 h-4 w-4" }), "New Article"]
|
|
421
|
+
onClick: de,
|
|
422
|
+
children: [/* @__PURE__ */ k(x, { className: "mr-2 h-4 w-4" }), "New Article"]
|
|
404
423
|
})
|
|
405
424
|
}),
|
|
406
|
-
/* @__PURE__ */
|
|
407
|
-
/* @__PURE__ */
|
|
425
|
+
/* @__PURE__ */ k(F, { children: "Capture the fixes and runbooks your team relies on so the next person doesn't start from scratch. Link articles to incidents and assets, then search them when troubleshooting." }),
|
|
426
|
+
/* @__PURE__ */ k(se, {
|
|
408
427
|
open: e,
|
|
409
428
|
onOpenChange: n,
|
|
410
|
-
article:
|
|
411
|
-
onSaved: () => void
|
|
429
|
+
article: d,
|
|
430
|
+
onSaved: () => void V()
|
|
412
431
|
}),
|
|
413
|
-
/* @__PURE__ */
|
|
414
|
-
article:
|
|
415
|
-
onOpenChange: (e) => !e &&
|
|
416
|
-
onEdit:
|
|
417
|
-
I(null), d(e), n(!0);
|
|
418
|
-
}
|
|
432
|
+
/* @__PURE__ */ k(le, {
|
|
433
|
+
article: L,
|
|
434
|
+
onOpenChange: (e) => !e && R(null),
|
|
435
|
+
onEdit: fe
|
|
419
436
|
}),
|
|
420
|
-
/* @__PURE__ */
|
|
437
|
+
/* @__PURE__ */ A("div", {
|
|
421
438
|
className: "relative",
|
|
422
|
-
children: [/* @__PURE__ */
|
|
439
|
+
children: [/* @__PURE__ */ k(S, { className: "absolute left-3.5 top-1/2 h-4.5 w-4.5 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ k(N, {
|
|
423
440
|
className: "h-12 pl-11 text-base",
|
|
424
441
|
placeholder: "Search runbooks, fixes and procedures…",
|
|
425
|
-
value:
|
|
426
|
-
onChange: (e) =>
|
|
442
|
+
value: p,
|
|
443
|
+
onChange: (e) => _(e.target.value),
|
|
427
444
|
"aria-label": "Search knowledge articles"
|
|
428
445
|
})]
|
|
429
446
|
}),
|
|
430
|
-
|
|
447
|
+
q.length > 0 && (ce.length > 0 || ue.length > 0) ? /* @__PURE__ */ A("div", {
|
|
431
448
|
className: "grid gap-4 lg:grid-cols-2",
|
|
432
|
-
children: [/* @__PURE__ */
|
|
449
|
+
children: [/* @__PURE__ */ k(l, {
|
|
433
450
|
title: "Publishing status",
|
|
434
451
|
subtitle: "Articles by status",
|
|
435
|
-
children: /* @__PURE__ */
|
|
436
|
-
data:
|
|
452
|
+
children: /* @__PURE__ */ k(o, {
|
|
453
|
+
data: ce,
|
|
437
454
|
height: 200,
|
|
438
455
|
centerLabel: "Articles",
|
|
439
|
-
centerValue:
|
|
456
|
+
centerValue: q.length,
|
|
440
457
|
ariaLabel: "Articles by status"
|
|
441
458
|
})
|
|
442
|
-
}), /* @__PURE__ */
|
|
459
|
+
}), /* @__PURE__ */ k(l, {
|
|
443
460
|
title: "Most consulted",
|
|
444
461
|
subtitle: "Top articles by views",
|
|
445
|
-
children: /* @__PURE__ */
|
|
446
|
-
data:
|
|
462
|
+
children: /* @__PURE__ */ k(a, {
|
|
463
|
+
data: ue,
|
|
447
464
|
xKey: "name",
|
|
448
465
|
series: [{
|
|
449
466
|
key: "views",
|
|
@@ -457,89 +474,89 @@ function ce() {
|
|
|
457
474
|
})
|
|
458
475
|
})]
|
|
459
476
|
}) : null,
|
|
460
|
-
/* @__PURE__ */
|
|
477
|
+
/* @__PURE__ */ A("div", {
|
|
461
478
|
className: "grid gap-5 lg:grid-cols-[200px_1fr]",
|
|
462
|
-
children: [/* @__PURE__ */
|
|
479
|
+
children: [/* @__PURE__ */ A("aside", {
|
|
463
480
|
className: "space-y-3",
|
|
464
481
|
children: [
|
|
465
|
-
/* @__PURE__ */
|
|
482
|
+
/* @__PURE__ */ A("div", {
|
|
466
483
|
className: "flex items-center justify-between gap-2",
|
|
467
|
-
children: [/* @__PURE__ */
|
|
484
|
+
children: [/* @__PURE__ */ k("h3", {
|
|
468
485
|
className: "text-sm font-semibold text-text-primary",
|
|
469
486
|
children: "Category"
|
|
470
|
-
}), /* @__PURE__ */
|
|
471
|
-
value:
|
|
472
|
-
onValueChange: (e) =>
|
|
473
|
-
children: [/* @__PURE__ */
|
|
487
|
+
}), /* @__PURE__ */ A(H, {
|
|
488
|
+
value: O,
|
|
489
|
+
onValueChange: (e) => I(e),
|
|
490
|
+
children: [/* @__PURE__ */ k(G, {
|
|
474
491
|
className: "h-8 w-[130px] text-xs lg:hidden",
|
|
475
|
-
children: /* @__PURE__ */
|
|
476
|
-
}), /* @__PURE__ */
|
|
492
|
+
children: /* @__PURE__ */ k(K, {})
|
|
493
|
+
}), /* @__PURE__ */ A(U, { children: [/* @__PURE__ */ k(W, {
|
|
477
494
|
value: "recent",
|
|
478
495
|
children: "Recently updated"
|
|
479
|
-
}), /* @__PURE__ */
|
|
496
|
+
}), /* @__PURE__ */ k(W, {
|
|
480
497
|
value: "views",
|
|
481
498
|
children: "Most viewed"
|
|
482
499
|
})] })]
|
|
483
500
|
})]
|
|
484
501
|
}),
|
|
485
|
-
/* @__PURE__ */
|
|
502
|
+
/* @__PURE__ */ A("div", {
|
|
486
503
|
className: "flex gap-2 overflow-x-auto pb-1 lg:flex-col lg:overflow-visible lg:pb-0",
|
|
487
|
-
children: [/* @__PURE__ */
|
|
504
|
+
children: [/* @__PURE__ */ A("button", {
|
|
488
505
|
type: "button",
|
|
489
|
-
onClick: () =>
|
|
490
|
-
className: r("shrink-0 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full",
|
|
491
|
-
children: ["All articles", /* @__PURE__ */
|
|
506
|
+
onClick: () => b("ALL"),
|
|
507
|
+
className: r("shrink-0 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full", y === "ALL" ? "bg-action-primary-bg/10 text-primary" : "text-text-secondary hover:text-text-primary"),
|
|
508
|
+
children: ["All articles", /* @__PURE__ */ k("span", {
|
|
492
509
|
className: "ml-1.5 text-xs text-text-muted tabular-nums",
|
|
493
|
-
children:
|
|
510
|
+
children: q.length
|
|
494
511
|
})]
|
|
495
|
-
}),
|
|
512
|
+
}), re.map(([e, t]) => /* @__PURE__ */ A("button", {
|
|
496
513
|
type: "button",
|
|
497
|
-
onClick: () =>
|
|
498
|
-
className: r("flex shrink-0 items-center justify-between gap-2 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full",
|
|
499
|
-
children: [/* @__PURE__ */
|
|
514
|
+
onClick: () => b(e),
|
|
515
|
+
className: r("flex shrink-0 items-center justify-between gap-2 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full", y === e ? "bg-action-primary-bg/10 text-primary" : "text-text-secondary hover:text-text-primary"),
|
|
516
|
+
children: [/* @__PURE__ */ k("span", {
|
|
500
517
|
className: "truncate",
|
|
501
518
|
children: e
|
|
502
|
-
}), /* @__PURE__ */
|
|
519
|
+
}), /* @__PURE__ */ k("span", {
|
|
503
520
|
className: "text-xs text-text-muted tabular-nums",
|
|
504
521
|
children: t
|
|
505
522
|
})]
|
|
506
523
|
}, e))]
|
|
507
524
|
}),
|
|
508
|
-
/* @__PURE__ */
|
|
525
|
+
/* @__PURE__ */ A("div", {
|
|
509
526
|
className: "hidden space-y-1.5 border-t border-border-seam pt-3 lg:block",
|
|
510
527
|
children: [
|
|
511
|
-
/* @__PURE__ */
|
|
528
|
+
/* @__PURE__ */ k(P, {
|
|
512
529
|
className: "text-xs",
|
|
513
530
|
children: "Status"
|
|
514
531
|
}),
|
|
515
|
-
/* @__PURE__ */
|
|
516
|
-
value:
|
|
517
|
-
onValueChange:
|
|
518
|
-
children: [/* @__PURE__ */
|
|
532
|
+
/* @__PURE__ */ A(H, {
|
|
533
|
+
value: w,
|
|
534
|
+
onValueChange: T,
|
|
535
|
+
children: [/* @__PURE__ */ k(G, {
|
|
519
536
|
className: "h-9",
|
|
520
|
-
children: /* @__PURE__ */
|
|
521
|
-
}), /* @__PURE__ */
|
|
537
|
+
children: /* @__PURE__ */ k(K, {})
|
|
538
|
+
}), /* @__PURE__ */ A(U, { children: [/* @__PURE__ */ k(W, {
|
|
522
539
|
value: "ALL",
|
|
523
540
|
children: "All statuses"
|
|
524
|
-
}),
|
|
541
|
+
}), ae.map((e) => /* @__PURE__ */ k(W, {
|
|
525
542
|
value: e,
|
|
526
543
|
children: e
|
|
527
544
|
}, e))] })]
|
|
528
545
|
}),
|
|
529
|
-
/* @__PURE__ */
|
|
546
|
+
/* @__PURE__ */ k(P, {
|
|
530
547
|
className: "text-xs",
|
|
531
548
|
children: "Sort"
|
|
532
549
|
}),
|
|
533
|
-
/* @__PURE__ */
|
|
534
|
-
value:
|
|
535
|
-
onValueChange: (e) =>
|
|
536
|
-
children: [/* @__PURE__ */
|
|
550
|
+
/* @__PURE__ */ A(H, {
|
|
551
|
+
value: O,
|
|
552
|
+
onValueChange: (e) => I(e),
|
|
553
|
+
children: [/* @__PURE__ */ k(G, {
|
|
537
554
|
className: "h-9",
|
|
538
|
-
children: /* @__PURE__ */
|
|
539
|
-
}), /* @__PURE__ */
|
|
555
|
+
children: /* @__PURE__ */ k(K, {})
|
|
556
|
+
}), /* @__PURE__ */ A(U, { children: [/* @__PURE__ */ k(W, {
|
|
540
557
|
value: "recent",
|
|
541
558
|
children: "Recently updated"
|
|
542
|
-
}), /* @__PURE__ */
|
|
559
|
+
}), /* @__PURE__ */ k(W, {
|
|
543
560
|
value: "views",
|
|
544
561
|
children: "Most viewed"
|
|
545
562
|
})] })]
|
|
@@ -547,73 +564,73 @@ function ce() {
|
|
|
547
564
|
]
|
|
548
565
|
})
|
|
549
566
|
]
|
|
550
|
-
}), /* @__PURE__ */
|
|
551
|
-
|
|
567
|
+
}), /* @__PURE__ */ A("div", { children: [
|
|
568
|
+
B && q.length === 0 ? /* @__PURE__ */ k("div", {
|
|
552
569
|
className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3",
|
|
553
|
-
children: Array.from({ length: 6 }).map((e, t) => /* @__PURE__ */
|
|
570
|
+
children: Array.from({ length: 6 }).map((e, t) => /* @__PURE__ */ k("div", { className: "h-40 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer" }, t))
|
|
554
571
|
}) : null,
|
|
555
|
-
!
|
|
556
|
-
illustration:
|
|
557
|
-
title:
|
|
558
|
-
description:
|
|
572
|
+
!B && Y.length === 0 ? /* @__PURE__ */ k(M, {
|
|
573
|
+
illustration: p || y !== "ALL" || w !== "ALL" ? "empty-search" : "empty-data",
|
|
574
|
+
title: p || y !== "ALL" || w !== "ALL" ? "No articles match your filters" : "No knowledge articles found",
|
|
575
|
+
description: p || y !== "ALL" || w !== "ALL" ? "Try a different term, category or status." : "Publish an article to start building your knowledge base."
|
|
559
576
|
}) : null,
|
|
560
|
-
/* @__PURE__ */
|
|
577
|
+
/* @__PURE__ */ k("div", {
|
|
561
578
|
className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3",
|
|
562
|
-
children:
|
|
579
|
+
children: $.pageItems.map((e) => /* @__PURE__ */ k(ee, {
|
|
563
580
|
treatment: "glass",
|
|
564
581
|
className: "border-border-seam",
|
|
565
|
-
children: /* @__PURE__ */
|
|
582
|
+
children: /* @__PURE__ */ A("button", {
|
|
566
583
|
type: "button",
|
|
567
|
-
onClick: () =>
|
|
584
|
+
onClick: () => pe(e),
|
|
568
585
|
className: "flex h-full w-full flex-col gap-3 p-4 text-left",
|
|
569
586
|
children: [
|
|
570
|
-
/* @__PURE__ */
|
|
587
|
+
/* @__PURE__ */ A("div", {
|
|
571
588
|
className: "flex flex-wrap items-center gap-1.5",
|
|
572
|
-
children: [/* @__PURE__ */
|
|
589
|
+
children: [/* @__PURE__ */ k(Q, { status: e.status }), e.tags.slice(0, 1).map((e) => /* @__PURE__ */ k("span", {
|
|
573
590
|
className: "rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary",
|
|
574
591
|
children: e
|
|
575
592
|
}, e))]
|
|
576
593
|
}),
|
|
577
|
-
/* @__PURE__ */
|
|
594
|
+
/* @__PURE__ */ k("h2", {
|
|
578
595
|
className: "line-clamp-2 text-base font-semibold text-text-primary",
|
|
579
596
|
children: e.title
|
|
580
597
|
}),
|
|
581
|
-
/* @__PURE__ */
|
|
598
|
+
/* @__PURE__ */ k("p", {
|
|
582
599
|
className: "line-clamp-2 flex-1 text-sm text-text-secondary",
|
|
583
|
-
children:
|
|
600
|
+
children: Z(e)
|
|
584
601
|
}),
|
|
585
|
-
/* @__PURE__ */
|
|
602
|
+
/* @__PURE__ */ A("div", {
|
|
586
603
|
className: "flex items-center justify-between gap-2 border-t border-border-seam/60 pt-3",
|
|
587
|
-
children: [/* @__PURE__ */
|
|
604
|
+
children: [/* @__PURE__ */ A("div", {
|
|
588
605
|
className: "flex min-w-0 items-center gap-2",
|
|
589
|
-
children: [/* @__PURE__ */
|
|
606
|
+
children: [/* @__PURE__ */ k("span", {
|
|
590
607
|
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-[10px] font-semibold text-text-secondary",
|
|
591
|
-
children: e.updatedBy ?
|
|
592
|
-
}), /* @__PURE__ */
|
|
608
|
+
children: e.updatedBy ? oe(e.updatedBy) : /* @__PURE__ */ k(C, { className: "h-3 w-3" })
|
|
609
|
+
}), /* @__PURE__ */ A("span", {
|
|
593
610
|
className: "truncate text-xs text-text-secondary",
|
|
594
|
-
children: ["Updated ",
|
|
611
|
+
children: ["Updated ", X(e.updatedAt)]
|
|
595
612
|
})]
|
|
596
|
-
}), /* @__PURE__ */
|
|
613
|
+
}), /* @__PURE__ */ A("span", {
|
|
597
614
|
className: "flex shrink-0 items-center gap-1 text-xs text-text-muted tabular-nums",
|
|
598
|
-
children: [/* @__PURE__ */
|
|
615
|
+
children: [/* @__PURE__ */ k(v, { className: "h-3.5 w-3.5" }), e.views.toLocaleString()]
|
|
599
616
|
})]
|
|
600
617
|
})
|
|
601
618
|
]
|
|
602
619
|
})
|
|
603
620
|
}, e.id))
|
|
604
621
|
}),
|
|
605
|
-
/* @__PURE__ */
|
|
622
|
+
/* @__PURE__ */ k("div", {
|
|
606
623
|
className: "mt-4",
|
|
607
|
-
children: /* @__PURE__ */
|
|
608
|
-
page:
|
|
609
|
-
pageCount:
|
|
610
|
-
rangeStart:
|
|
611
|
-
rangeEnd:
|
|
612
|
-
totalItems:
|
|
613
|
-
canPrev:
|
|
614
|
-
canNext:
|
|
615
|
-
onPrev:
|
|
616
|
-
onNext:
|
|
624
|
+
children: /* @__PURE__ */ k(s, {
|
|
625
|
+
page: $.page,
|
|
626
|
+
pageCount: $.pageCount,
|
|
627
|
+
rangeStart: $.rangeStart,
|
|
628
|
+
rangeEnd: $.rangeEnd,
|
|
629
|
+
totalItems: $.totalItems,
|
|
630
|
+
canPrev: $.canPrev,
|
|
631
|
+
canNext: $.canNext,
|
|
632
|
+
onPrev: $.prevPage,
|
|
633
|
+
onNext: $.nextPage,
|
|
617
634
|
itemLabel: "articles"
|
|
618
635
|
})
|
|
619
636
|
})
|
|
@@ -623,6 +640,6 @@ function ce() {
|
|
|
623
640
|
});
|
|
624
641
|
}
|
|
625
642
|
//#endregion
|
|
626
|
-
export {
|
|
643
|
+
export { ue as KnowledgeBasePage };
|
|
627
644
|
|
|
628
645
|
//# sourceMappingURL=KnowledgeBasePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnowledgeBasePage.js","names":[],"sources":["../../../src/assethandler/pages/KnowledgeBasePage.tsx"],"sourcesContent":["import { BookOpen, Plus, Search, Eye, User, Link2, ExternalLink, Pencil, X } from 'lucide-react';\nimport {\n Button,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Pagination,\n usePagination,\n SectionHeader,\n ChartCard,\n DonutChart,\n BarsChart,\n seriesColor,\n type DonutSlice,\n} from '../components';\nimport { useAssetHandlerNavigate } from '../hooks/useAssetHandlerNavigate';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { cn } from '../utils/cn';\nimport { notify } from '../utils/toast';\nimport { formatAssetOption, formatIncidentOption, parseTagsInput } from '../utils/knowledgeBase';\n\nconst KNOWLEDGE_ARTICLES_QUERY = gql`\n query AssetHandlerKnowledgeArticles {\n knowledgeArticles {\n id\n title\n slug\n summary\n content\n status\n tags\n views\n createdBy\n updatedBy\n publishedAt\n createdAt\n updatedAt\n relatedAssetId\n relatedIncidentId\n }\n }\n`;\n\nconst CREATE_KNOWLEDGE_ARTICLE_MUTATION = gql`\n mutation AssetHandlerCreateKnowledgeArticle($input: CreateKnowledgeArticleInput!) {\n createKnowledgeArticle(input: $input) {\n id\n title\n }\n }\n`;\n\nconst UPDATE_KNOWLEDGE_ARTICLE_MUTATION = gql`\n mutation AssetHandlerUpdateKnowledgeArticle($id: ID!, $input: UpdateKnowledgeArticleInput!) {\n updateKnowledgeArticle(id: $id, input: $input) {\n id\n title\n }\n }\n`;\n\ninterface KnowledgeArticle {\n id: string;\n title: string;\n slug: string;\n summary: string | null;\n content: string;\n status: string;\n tags: string[];\n views: number;\n createdBy: string;\n updatedBy: string;\n publishedAt: string | null;\n createdAt: string;\n updatedAt: string;\n relatedAssetId: string | null;\n relatedIncidentId: string | null;\n}\n\ninterface CreateKnowledgeArticleResult {\n createKnowledgeArticle: { id: string; title: string };\n}\n\ninterface UpdateKnowledgeArticleResult {\n updateKnowledgeArticle: { id: string; title: string };\n}\n\nconst ARTICLE_STATUSES = ['DRAFT', 'PUBLISHED', 'ARCHIVED'] as const;\n\nconst STATUS_META: Record<string, { label: string; className: string }> = {\n PUBLISHED: {\n label: 'Published',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n },\n DRAFT: { label: 'Draft', className: 'bg-bg-sunken text-text-secondary' },\n ARCHIVED: {\n label: 'Archived',\n className: 'bg-status-warning-bg-subtle text-status-warning-text',\n },\n};\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\nconst formatDate = (value: string | null): string =>\n value\n ? new Date(value).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n : '—';\n\n/** A 2-line excerpt: the summary if present, otherwise the opening of the body. */\nfunction excerptOf(article: KnowledgeArticle): string {\n if (article.summary && article.summary.trim()) return article.summary.trim();\n const body = article.content.replace(/\\s+/g, ' ').trim();\n return body.length > 160 ? `${body.slice(0, 160)}…` : body;\n}\n\n/** Short avatar initials from an actor id (best-effort, no fabricated names). */\nfunction initialsOf(actor: string): string {\n const clean = actor.replace(/[^a-zA-Z0-9]/g, '');\n return (clean.slice(0, 2) || '?').toUpperCase();\n}\n\nfunction StatusPill({ status }: { status: string }) {\n const meta = STATUS_META[status] ?? {\n label: status,\n className: 'bg-bg-sunken text-text-secondary',\n };\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium',\n meta.className\n )}\n >\n {meta.label}\n </span>\n );\n}\n\nfunction ArticleDialog({\n open,\n onOpenChange,\n article,\n onSaved,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n article: KnowledgeArticle | null;\n onSaved: () => void;\n}) {\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-2xl\">\n {open && (\n <ArticleForm\n key={article?.id ?? 'new'}\n article={article}\n onOpenChange={onOpenChange}\n onSaved={onSaved}\n />\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nfunction ArticleForm({\n article,\n onOpenChange,\n onSaved,\n}: {\n article: KnowledgeArticle | null;\n onOpenChange: (open: boolean) => void;\n onSaved: () => void;\n}) {\n const isEdit = Boolean(article);\n const { assets, incidents } = useAssetHandlerStore();\n\n const [title, setTitle] = useState(article?.title ?? '');\n const [slug, setSlug] = useState(article?.slug ?? '');\n const [slugTouched, setSlugTouched] = useState(isEdit);\n const [summary, setSummary] = useState(article?.summary ?? '');\n const [content, setContent] = useState(article?.content ?? '');\n const [tags, setTags] = useState(article ? article.tags.join(', ') : '');\n const [status, setStatus] = useState<string>(article?.status ?? 'PUBLISHED');\n const [relatedAssetId, setRelatedAssetId] = useState(article?.relatedAssetId ?? '');\n const [relatedIncidentId, setRelatedIncidentId] = useState(article?.relatedIncidentId ?? '');\n\n const reset = () => {\n setTitle('');\n setSlug('');\n setSlugTouched(false);\n setSummary('');\n setContent('');\n setTags('');\n setStatus('PUBLISHED');\n setRelatedAssetId('');\n setRelatedIncidentId('');\n };\n\n const [createArticle, { loading: creating }] = useMutation<CreateKnowledgeArticleResult>(\n CREATE_KNOWLEDGE_ARTICLE_MUTATION,\n {\n onCompleted: (data) => {\n notify.success(\n 'Article published',\n `${data.createKnowledgeArticle.title} is now available.`\n );\n reset();\n onOpenChange(false);\n onSaved();\n },\n onError: (error) => notify.error('Could not publish article', error.message),\n }\n );\n\n const [updateArticle, { loading: updating }] = useMutation<UpdateKnowledgeArticleResult>(\n UPDATE_KNOWLEDGE_ARTICLE_MUTATION,\n {\n refetchQueries: ['AssetHandlerKnowledgeArticles'],\n onCompleted: (data) => {\n notify.success('Article updated', `${data.updateKnowledgeArticle.title} has been saved.`);\n onOpenChange(false);\n onSaved();\n },\n onError: (error) => notify.error('Could not update article', error.message),\n }\n );\n\n const selectedAsset = useMemo(\n () => assets.find((a) => a.id === relatedAssetId),\n [assets, relatedAssetId]\n );\n const selectedIncident = useMemo(\n () => incidents.find((i) => i.id === relatedIncidentId),\n [incidents, relatedIncidentId]\n );\n\n const handleTitleChange = (next: string) => {\n setTitle(next);\n if (!slugTouched) setSlug(slugify(next));\n };\n\n const handleSubmit = () => {\n if (!title.trim() || !slug.trim()) {\n notify.warning('Missing details', 'Title and slug are required.');\n return;\n }\n if (!content.trim()) {\n notify.warning('Body required', 'Write the article body before publishing.');\n return;\n }\n\n const parsedTags = parseTagsInput(tags);\n const baseInput = {\n title: title.trim(),\n summary: summary.trim() || null,\n content: content.trim(),\n tags: parsedTags,\n status,\n relatedAssetId: relatedAssetId || null,\n relatedIncidentId: relatedIncidentId || null,\n };\n\n if (isEdit && article) {\n void updateArticle({\n variables: { id: article.id, input: baseInput },\n });\n } else {\n void createArticle({\n variables: {\n input: { ...baseInput, slug: slug.trim() },\n },\n });\n }\n };\n\n return (\n <>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{isEdit ? 'Edit article' : 'New Article'}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {isEdit\n ? 'Update the runbook or fix so the team has the latest guidance.'\n : 'Capture a runbook or fix so your team can find it later.'}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Title *</Label>\n <Input value={title} onChange={(e) => handleTitleChange(e.target.value)} />\n </div>\n <div className=\"space-y-2\">\n <Label>Slug *</Label>\n <Input\n value={slug}\n disabled={isEdit}\n onChange={(e) => {\n setSlugTouched(true);\n setSlug(e.target.value);\n }}\n />\n </div>\n </div>\n <div className=\"space-y-2\">\n <Label>Summary</Label>\n <Input\n placeholder=\"One-line description\"\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Body *</Label>\n <Textarea\n placeholder=\"Write the full article body — steps, context, links…\"\n value={content}\n onChange={(e) => setContent(e.target.value)}\n rows={8}\n />\n </div>\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Category tags</Label>\n <Input\n placeholder=\"Runbooks, Fixes, Procedures…\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Status</Label>\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ARTICLE_STATUSES.map((s) => (\n <SelectItem key={s} value={s}>\n {s}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Related asset</Label>\n <Select value={relatedAssetId} onValueChange={setRelatedAssetId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">None</SelectItem>\n {assets.map((asset) => (\n <SelectItem key={asset.id} value={asset.id}>\n {formatAssetOption(asset)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {selectedAsset ? (\n <div className=\"flex flex-wrap gap-2\">\n <span className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary\">\n {formatAssetOption(selectedAsset)}\n <button\n type=\"button\"\n onClick={() => setRelatedAssetId('')}\n className=\"rounded-full p-0.5 hover:bg-action-primary-bg/20\"\n aria-label=\"Clear related asset\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n </div>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <Label>Related incident</Label>\n <Select value={relatedIncidentId} onValueChange={setRelatedIncidentId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select an incident\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">None</SelectItem>\n {incidents.map((incident) => (\n <SelectItem key={incident.id} value={incident.id}>\n {formatIncidentOption(incident)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {selectedIncident ? (\n <div className=\"flex flex-wrap gap-2\">\n <span className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary\">\n {formatIncidentOption(selectedIncident)}\n <button\n type=\"button\"\n onClick={() => setRelatedIncidentId('')}\n className=\"rounded-full p-0.5 hover:bg-action-primary-bg/20\"\n aria-label=\"Clear related incident\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n </div>\n ) : null}\n </div>\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={creating || updating}\n >\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={creating || updating}>\n {isEdit\n ? updating\n ? 'Saving…'\n : 'Save changes'\n : creating\n ? 'Publishing…'\n : 'Publish article'}\n </Button>\n </ResponsiveDialogFooter>\n </>\n );\n}\n\nfunction ArticleReader({\n article,\n onOpenChange,\n onEdit,\n}: {\n article: KnowledgeArticle | null;\n onOpenChange: (open: boolean) => void;\n onEdit: (article: KnowledgeArticle) => void;\n}) {\n const navigate = useAssetHandlerNavigate();\n return (\n <ResponsiveDialog open={Boolean(article)} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-2xl\">\n {article ? (\n <>\n <ResponsiveDialogHeader>\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusPill status={article.status} />\n {article.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary\"\n >\n {tag}\n </span>\n ))}\n </div>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0 flex-1\">\n <ResponsiveDialogTitle className=\"mt-1\">{article.title}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Updated {formatDate(article.updatedAt)} · {article.views.toLocaleString()} views\n </ResponsiveDialogDescription>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n onOpenChange(false);\n onEdit(article);\n }}\n >\n <Pencil className=\"mr-1.5 h-3.5 w-3.5\" />\n Edit\n </Button>\n </div>\n </ResponsiveDialogHeader>\n <div className=\"whitespace-pre-wrap text-sm leading-relaxed text-text-primary\">\n {article.content}\n </div>\n {article.relatedAssetId || article.relatedIncidentId ? (\n <div className=\"mt-4 space-y-2 border-t border-border-seam pt-4\">\n <h4 className=\"flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n <Link2 className=\"h-3.5 w-3.5\" />\n Linked records\n </h4>\n <div className=\"flex flex-wrap gap-2\">\n {article.relatedAssetId ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n onOpenChange(false);\n navigate(`/assets/${article.relatedAssetId}`);\n }}\n >\n View linked asset\n <ExternalLink className=\"ml-1.5 h-3.5 w-3.5\" />\n </Button>\n ) : null}\n {article.relatedIncidentId ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n onOpenChange(false);\n navigate(`/itil/incidents/${article.relatedIncidentId}`);\n }}\n >\n View linked incident\n <ExternalLink className=\"ml-1.5 h-3.5 w-3.5\" />\n </Button>\n ) : null}\n </div>\n </div>\n ) : null}\n </>\n ) : null}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\ntype SortKey = 'recent' | 'views';\n\nexport function KnowledgeBasePage() {\n const [isDialogOpen, setIsDialogOpen] = useState(false);\n const [dialogArticle, setDialogArticle] = useState<KnowledgeArticle | null>(null);\n const [search, setSearch] = useState('');\n const [category, setCategory] = useState<string>('ALL');\n const [statusFilter, setStatusFilter] = useState<string>('ALL');\n const [sort, setSort] = useState<SortKey>('recent');\n const [reading, setReading] = useState<KnowledgeArticle | null>(null);\n\n const { data, loading, refetch } = useQuery<{ knowledgeArticles: KnowledgeArticle[] }>(\n KNOWLEDGE_ARTICLES_QUERY,\n { fetchPolicy: 'cache-and-network' }\n );\n\n const articles = useMemo(() => data?.knowledgeArticles ?? [], [data?.knowledgeArticles]);\n\n const categories = useMemo(() => {\n const counts = new Map<string, number>();\n for (const a of articles) {\n for (const tag of a.tags) counts.set(tag, (counts.get(tag) ?? 0) + 1);\n }\n return [...counts.entries()].sort((a, b) => b[1] - a[1]);\n }, [articles]);\n\n const filtered = useMemo(() => {\n const term = search.trim().toLowerCase();\n const list = articles.filter((article) => {\n if (category !== 'ALL' && !article.tags.includes(category)) return false;\n if (statusFilter !== 'ALL' && article.status !== statusFilter) return false;\n if (!term) return true;\n const haystack = [\n article.title,\n article.summary ?? '',\n article.content,\n article.tags.join(' '),\n ]\n .join(' ')\n .toLowerCase();\n return haystack.includes(term);\n });\n return [...list].sort((a, b) =>\n sort === 'views'\n ? b.views - a.views\n : new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n );\n }, [articles, search, category, statusFilter, sort]);\n\n const statusMix = useMemo<DonutSlice[]>(() => {\n const counts = articles.reduce<Record<string, number>>((acc, a) => {\n acc[a.status] = (acc[a.status] ?? 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts).map(([status, value], i) => ({\n name: STATUS_META[status]?.label ?? status,\n value,\n color: seriesColor(i),\n }));\n }, [articles]);\n\n const topByViews = useMemo(\n () =>\n [...articles]\n .filter((a) => a.views > 0)\n .sort((a, b) => b.views - a.views)\n .slice(0, 6)\n .map((a) => ({ name: a.title, views: a.views })),\n [articles]\n );\n\n const pagination = usePagination(filtered, {\n pageSize: 9,\n resetKey: `${search}|${category}|${statusFilter}|${sort}`,\n });\n\n const handleNewArticle = () => {\n setDialogArticle(null);\n setIsDialogOpen(true);\n };\n\n const handleEditArticle = (article: KnowledgeArticle) => {\n setReading(null);\n setDialogArticle(article);\n setIsDialogOpen(true);\n };\n\n return (\n <div className=\"space-y-5 p-4 sm:p-6\">\n <SectionHeader\n hero\n icon={BookOpen}\n title=\"Knowledge Base\"\n description=\"Turn one-off fixes and runbooks into repeatable, searchable operational knowledge.\"\n actions={\n <Button className=\"w-full sm:w-auto\" onClick={handleNewArticle}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New Article\n </Button>\n }\n />\n <PagePurpose>\n Capture the fixes and runbooks your team relies on so the next person doesn't start\n from scratch. Link articles to incidents and assets, then search them when troubleshooting.\n </PagePurpose>\n\n <ArticleDialog\n open={isDialogOpen}\n onOpenChange={setIsDialogOpen}\n article={dialogArticle}\n onSaved={() => void refetch()}\n />\n <ArticleReader\n article={reading}\n onOpenChange={(open) => !open && setReading(null)}\n onEdit={handleEditArticle}\n />\n\n <div className=\"relative\">\n <Search className=\"absolute left-3.5 top-1/2 h-4.5 w-4.5 -translate-y-1/2 text-text-secondary\" />\n <Input\n className=\"h-12 pl-11 text-base\"\n placeholder=\"Search runbooks, fixes and procedures…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n aria-label=\"Search knowledge articles\"\n />\n </div>\n\n {articles.length > 0 && (statusMix.length > 0 || topByViews.length > 0) ? (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <ChartCard title=\"Publishing status\" subtitle=\"Articles by status\">\n <DonutChart\n data={statusMix}\n height={200}\n centerLabel=\"Articles\"\n centerValue={articles.length}\n ariaLabel=\"Articles by status\"\n />\n </ChartCard>\n <ChartCard title=\"Most consulted\" subtitle=\"Top articles by views\">\n <BarsChart\n data={topByViews}\n xKey=\"name\"\n series={[{ key: 'views', label: 'Views' }]}\n orientation=\"horizontal\"\n height={200}\n categoryWidth={150}\n emptyLabel=\"No views recorded yet\"\n ariaLabel=\"Top articles by views\"\n />\n </ChartCard>\n </div>\n ) : null}\n\n <div className=\"grid gap-5 lg:grid-cols-[200px_1fr]\">\n {/* Category facet rail */}\n <aside className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Category</h3>\n <Select value={sort} onValueChange={(v) => setSort(v as SortKey)}>\n <SelectTrigger className=\"h-8 w-[130px] text-xs lg:hidden\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"recent\">Recently updated</SelectItem>\n <SelectItem value=\"views\">Most viewed</SelectItem>\n </SelectContent>\n </Select>\n </div>\n {/* Mobile: horizontal chips; desktop: vertical list */}\n <div className=\"flex gap-2 overflow-x-auto pb-1 lg:flex-col lg:overflow-visible lg:pb-0\">\n <button\n type=\"button\"\n onClick={() => setCategory('ALL')}\n className={cn(\n 'shrink-0 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full',\n category === 'ALL'\n ? 'bg-action-primary-bg/10 text-primary'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n All articles\n <span className=\"ml-1.5 text-xs text-text-muted tabular-nums\">{articles.length}</span>\n </button>\n {categories.map(([tag, count]) => (\n <button\n key={tag}\n type=\"button\"\n onClick={() => setCategory(tag)}\n className={cn(\n 'flex shrink-0 items-center justify-between gap-2 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full',\n category === tag\n ? 'bg-action-primary-bg/10 text-primary'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n <span className=\"truncate\">{tag}</span>\n <span className=\"text-xs text-text-muted tabular-nums\">{count}</span>\n </button>\n ))}\n </div>\n <div className=\"hidden space-y-1.5 border-t border-border-seam pt-3 lg:block\">\n <Label className=\"text-xs\">Status</Label>\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger className=\"h-9\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All statuses</SelectItem>\n {ARTICLE_STATUSES.map((s) => (\n <SelectItem key={s} value={s}>\n {s}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Label className=\"text-xs\">Sort</Label>\n <Select value={sort} onValueChange={(v) => setSort(v as SortKey)}>\n <SelectTrigger className=\"h-9\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"recent\">Recently updated</SelectItem>\n <SelectItem value=\"views\">Most viewed</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </aside>\n\n {/* Article grid */}\n <div>\n {loading && articles.length === 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div\n key={i}\n className=\"h-40 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : null}\n {!loading && filtered.length === 0 ? (\n <IllustratedEmptyState\n illustration={\n search || category !== 'ALL' || statusFilter !== 'ALL'\n ? 'empty-search'\n : 'empty-data'\n }\n title={\n search || category !== 'ALL' || statusFilter !== 'ALL'\n ? 'No articles match your filters'\n : 'No knowledge articles found'\n }\n description={\n search || category !== 'ALL' || statusFilter !== 'ALL'\n ? 'Try a different term, category or status.'\n : 'Publish an article to start building your knowledge base.'\n }\n />\n ) : null}\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {pagination.pageItems.map((article) => (\n <GlassCard key={article.id} treatment=\"glass\" className=\"border-border-seam\">\n <button\n type=\"button\"\n onClick={() => setReading(article)}\n className=\"flex h-full w-full flex-col gap-3 p-4 text-left\"\n >\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusPill status={article.status} />\n {article.tags.slice(0, 1).map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h2 className=\"line-clamp-2 text-base font-semibold text-text-primary\">\n {article.title}\n </h2>\n <p className=\"line-clamp-2 flex-1 text-sm text-text-secondary\">\n {excerptOf(article)}\n </p>\n <div className=\"flex items-center justify-between gap-2 border-t border-border-seam/60 pt-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-[10px] font-semibold text-text-secondary\">\n {article.updatedBy ? (\n initialsOf(article.updatedBy)\n ) : (\n <User className=\"h-3 w-3\" />\n )}\n </span>\n <span className=\"truncate text-xs text-text-secondary\">\n Updated {formatDate(article.updatedAt)}\n </span>\n </div>\n <span className=\"flex shrink-0 items-center gap-1 text-xs text-text-muted tabular-nums\">\n <Eye className=\"h-3.5 w-3.5\" />\n {article.views.toLocaleString()}\n </span>\n </div>\n </button>\n </GlassCard>\n ))}\n </div>\n <div className=\"mt-4\">\n <Pagination\n page={pagination.page}\n pageCount={pagination.pageCount}\n rangeStart={pagination.rangeStart}\n rangeEnd={pagination.rangeEnd}\n totalItems={pagination.totalItems}\n canPrev={pagination.canPrev}\n canNext={pagination.canNext}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"articles\"\n />\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAwCA,IAAM,KAA2B,CAAG;;;;;;;;;;;;;;;;;;;;GAsB9B,IAAoC,CAAG;;;;;;;GASvC,IAAoC,CAAG;;;;;;;GAmCvC,KAAmB;CAAC;CAAS;CAAa;AAAU,GAEpD,IAAoE;CACxE,WAAW;EACT,OAAO;EACP,WAAW;CACb;CACA,OAAO;EAAE,OAAO;EAAS,WAAW;CAAmC;CACvE,UAAU;EACR,OAAO;EACP,WAAW;CACb;AACF,GAEM,KAAW,MACf,EACG,YAAY,EACZ,KAAK,EACL,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,GAErB,KAAc,MAClB,IACI,IAAI,KAAK,CAAK,EAAE,mBAAmB,KAAA,GAAW;CAC5C,MAAM;CACN,OAAO;CACP,KAAK;AACP,CAAC,IACD;AAGN,SAAS,GAAU,GAAmC;CACpD,IAAI,EAAQ,WAAW,EAAQ,QAAQ,KAAK,GAAG,OAAO,EAAQ,QAAQ,KAAK;CAC3E,IAAM,IAAO,EAAQ,QAAQ,QAAQ,QAAQ,GAAG,EAAE,KAAK;CACvD,OAAO,EAAK,SAAS,MAAM,GAAG,EAAK,MAAM,GAAG,GAAG,EAAE,KAAK;AACxD;AAGA,SAAS,EAAW,GAAuB;CAEzC,QADc,EAAM,QAAQ,iBAAiB,EACrC,EAAM,MAAM,GAAG,CAAC,KAAK,KAAK,YAAY;AAChD;AAEA,SAAS,EAAW,EAAE,aAA8B;CAClD,IAAM,IAAO,EAAY,MAAW;EAClC,OAAO;EACP,WAAW;CACb;CACA,OACE,kBAAC,QAAD;EACE,WAAW,EACT,yEACA,EAAK,SACP;YAEC,EAAK;CACF,CAAA;AAEV;AAEA,SAAS,GAAc,EACrB,SACA,iBACA,YACA,cAMC;CACD,OACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAChC,KACC,kBAAC,IAAD;IAEW;IACK;IACL;GACV,GAJM,GAAS,MAAM,KAIrB;EAEoB,CAAA;CACT,CAAA;AAEtB;AAEA,SAAS,GAAY,EACnB,YACA,iBACA,cAKC;CACD,IAAM,IAAS,EAAQ,GACjB,EAAE,WAAQ,iBAAc,EAAqB,GAE7C,CAAC,GAAO,KAAY,EAAS,GAAS,SAAS,EAAE,GACjD,CAAC,GAAM,KAAW,EAAS,GAAS,QAAQ,EAAE,GAC9C,CAAC,GAAa,KAAkB,EAAS,CAAM,GAC/C,CAAC,GAAS,KAAc,EAAS,GAAS,WAAW,EAAE,GACvD,CAAC,GAAS,KAAc,EAAS,GAAS,WAAW,EAAE,GACvD,CAAC,GAAM,KAAW,EAAS,IAAU,EAAQ,KAAK,KAAK,IAAI,IAAI,EAAE,GACjE,CAAC,GAAQ,KAAa,EAAiB,GAAS,UAAU,WAAW,GACrE,CAAC,GAAgB,KAAqB,EAAS,GAAS,kBAAkB,EAAE,GAC5E,CAAC,GAAmB,KAAwB,EAAS,GAAS,qBAAqB,EAAE,GAErF,WAAc;EASlB,AARA,EAAS,EAAE,GACX,EAAQ,EAAE,GACV,EAAe,EAAK,GACpB,EAAW,EAAE,GACb,EAAW,EAAE,GACb,EAAQ,EAAE,GACV,EAAU,WAAW,GACrB,EAAkB,EAAE,GACpB,EAAqB,EAAE;CACzB,GAEM,CAAC,GAAe,EAAE,SAAS,OAAc,EAC7C,GACA;EACE,cAAc,MAAS;GAOrB,AANA,EAAO,QACL,qBACA,GAAG,EAAK,uBAAuB,MAAM,mBACvC,GACA,GAAM,GACN,EAAa,EAAK,GAClB,EAAQ;EACV;EACA,UAAU,MAAU,EAAO,MAAM,6BAA6B,EAAM,OAAO;CAC7E,CACF,GAEM,CAAC,IAAe,EAAE,SAAS,OAAc,EAC7C,GACA;EACE,gBAAgB,CAAC,+BAA+B;EAChD,cAAc,MAAS;GAGrB,AAFA,EAAO,QAAQ,mBAAmB,GAAG,EAAK,uBAAuB,MAAM,iBAAiB,GACxF,EAAa,EAAK,GAClB,EAAQ;EACV;EACA,UAAU,MAAU,EAAO,MAAM,4BAA4B,EAAM,OAAO;CAC5E,CACF,GAEM,IAAgB,QACd,EAAO,MAAM,MAAM,EAAE,OAAO,CAAc,GAChD,CAAC,GAAQ,CAAc,CACzB,GACM,KAAmB,QACjB,EAAU,MAAM,MAAM,EAAE,OAAO,CAAiB,GACtD,CAAC,GAAW,CAAiB,CAC/B,GAEM,MAAqB,MAAiB;EAE1C,AADA,EAAS,CAAI,GACR,KAAa,EAAQ,EAAQ,CAAI,CAAC;CACzC;CAoCA,OACE,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAwB,IAAS,iBAAiB,cAAqC,CAAA,GACvF,kBAAC,GAAD,EAAA,UACG,IACG,mEACA,2DACuB,CAAA,CACP,EAAA,CAAA;EACxB,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,UAAc,CAAA,GACrB,kBAAC,GAAD;OAAO,OAAO;OAAO,WAAW,MAAM,GAAkB,EAAE,OAAO,KAAK;MAAI,CAAA,CACvE;SACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,SAAa,CAAA,GACpB,kBAAC,GAAD;OACE,OAAO;OACP,UAAU;OACV,WAAW,MAAM;QAEf,AADA,EAAe,EAAI,GACnB,EAAQ,EAAE,OAAO,KAAK;OACxB;MACD,CAAA,CACE;OACF;;IACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAO,UAAc,CAAA,GACrB,kBAAC,GAAD;MACE,aAAY;MACZ,OAAO;MACP,WAAW,MAAM,EAAW,EAAE,OAAO,KAAK;KAC3C,CAAA,CACE;;IACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAO,SAAa,CAAA,GACpB,kBAAC,GAAD;MACE,aAAY;MACZ,OAAO;MACP,WAAW,MAAM,EAAW,EAAE,OAAO,KAAK;MAC1C,MAAM;KACP,CAAA,CACE;;IACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,gBAAoB,CAAA,GAC3B,kBAAC,GAAD;OACE,aAAY;OACZ,OAAO;OACP,WAAW,MAAM,EAAQ,EAAE,OAAO,KAAK;MACxC,CAAA,CACE;SACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,SAAa,CAAA,GACpB,kBAAC,GAAD;OAAQ,OAAO;OAAQ,eAAe;iBAAtC,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,CAAc,CAAA,EACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,GAAiB,KAAK,MACrB,kBAAC,GAAD;QAAoB,OAAO;kBACxB;OACS,GAFK,CAEL,CACb,EACY,CAAA,CACT;QACL;OACF;;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAA,UAAO,gBAAoB,CAAA;OAC3B,kBAAC,GAAD;QAAQ,OAAO;QAAgB,eAAe;kBAA9C,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,aAAY,kBAAmB,CAAA,EAC/B,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAY,OAAM;mBAAG;QAAgB,CAAA,GACpC,EAAO,KAAK,MACX,kBAAC,GAAD;SAA2B,OAAO,EAAM;mBACrC,EAAkB,CAAK;QACd,GAFK,EAAM,EAEX,CACb,CACY,EAAA,CAAA,CACT;;OACP,IACC,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACG,EAAkB,CAAa,GAChC,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAkB,EAAE;UACnC,WAAU;UACV,cAAW;oBAEX,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;SAClB,CAAA,CACJ;;OACH,CAAA,IACH;MACD;SACL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAA,UAAO,mBAAuB,CAAA;OAC9B,kBAAC,GAAD;QAAQ,OAAO;QAAmB,eAAe;kBAAjD,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,aAAY,qBAAsB,CAAA,EAClC,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAY,OAAM;mBAAG;QAAgB,CAAA,GACpC,EAAU,KAAK,MACd,kBAAC,GAAD;SAA8B,OAAO,EAAS;mBAC3C,EAAqB,CAAQ;QACpB,GAFK,EAAS,EAEd,CACb,CACY,EAAA,CAAA,CACT;;OACP,KACC,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACG,EAAqB,EAAgB,GACtC,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAqB,EAAE;UACtC,WAAU;UACV,cAAW;oBAEX,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;SAClB,CAAA,CACJ;;OACH,CAAA,IACH;MACD;OACF;;GACF;;EACL,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GACE,SAAQ;GACR,eAAe,EAAa,EAAK;GACjC,UAAU,KAAY;aACvB;EAEO,CAAA,GACR,kBAAC,GAAD;GAAQ,eAlLa;IACzB,IAAI,CAAC,EAAM,KAAK,KAAK,CAAC,EAAK,KAAK,GAAG;KACjC,EAAO,QAAQ,mBAAmB,8BAA8B;KAChE;IACF;IACA,IAAI,CAAC,EAAQ,KAAK,GAAG;KACnB,EAAO,QAAQ,iBAAiB,2CAA2C;KAC3E;IACF;IAEA,IAAM,IAAa,GAAe,CAAI,GAChC,IAAY;KAChB,OAAO,EAAM,KAAK;KAClB,SAAS,EAAQ,KAAK,KAAK;KAC3B,SAAS,EAAQ,KAAK;KACtB,MAAM;KACN;KACA,gBAAgB,KAAkB;KAClC,mBAAmB,KAAqB;IAC1C;IAEA,AAAI,KAAU,IACZ,GAAmB,EACjB,WAAW;KAAE,IAAI,EAAQ;KAAI,OAAO;IAAU,EAChD,CAAC,IAED,EAAmB,EACjB,WAAW,EACT,OAAO;KAAE,GAAG;KAAW,MAAM,EAAK,KAAK;IAAE,EAC3C,EACF,CAAC;GAEL;GAkJqC,UAAU,KAAY;aAClD,IACG,IACE,YACA,iBACF,IACE,gBACA;EACA,CAAA,CACc,EAAA,CAAA;CACxB,EAAA,CAAA;AAEN;AAEA,SAAS,GAAc,EACrB,YACA,iBACA,aAKC;CACD,IAAM,IAAW,EAAwB;CACzC,OACE,kBAAC,GAAD;EAAkB,MAAM,EAAQ;EAAwB;YACtD,kBAAC,GAAD;GAAyB,WAAU;aAChC,IACC,kBAAA,GAAA,EAAA,UAAA;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAY,QAAQ,EAAQ,OAAS,CAAA,GACpC,EAAQ,KAAK,KAAK,MACjB,kBAAC,QAAD;MAEE,WAAU;gBAET;KACG,GAJC,CAID,CACP,CACE;QACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAuB,WAAU;iBAAQ,EAAQ;MAA6B,CAAA,GAC9E,kBAAC,GAAD,EAAA,UAAA;OAA6B;OAClB,EAAW,EAAQ,SAAS;OAAE;OAAI,EAAQ,MAAM,eAAe;OAAE;MAC/C,EAAA,CAAA,CAC1B;SACL,kBAAC,GAAD;MACE,SAAQ;MACR,MAAK;MACL,eAAe;OAEb,AADA,EAAa,EAAK,GAClB,EAAO,CAAO;MAChB;gBANF,CAQE,kBAAC,GAAD,EAAQ,WAAU,qBAAsB,CAAA,GAAC,MAEnC;OACL;MACiB,EAAA,CAAA;IACxB,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAQ;IACN,CAAA;IACJ,EAAQ,kBAAkB,EAAQ,oBACjC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAd,CACE,kBAAC,GAAD,EAAO,WAAU,cAAe,CAAA,GAAC,gBAE/B;SACJ,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,EAAQ,iBACP,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,eAAe;QAEb,AADA,EAAa,EAAK,GAClB,EAAS,WAAW,EAAQ,gBAAgB;OAC9C;iBANF,CAOC,qBAEC,kBAAC,GAAD,EAAc,WAAU,qBAAsB,CAAA,CACxC;WACN,MACH,EAAQ,oBACP,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,eAAe;QAEb,AADA,EAAa,EAAK,GAClB,EAAS,mBAAmB,EAAQ,mBAAmB;OACzD;iBANF,CAOC,wBAEC,kBAAC,GAAD,EAAc,WAAU,qBAAsB,CAAA,CACxC;WACN,IACD;OACF;SACH;GACJ,EAAA,CAAA,IACA;EACmB,CAAA;CACT,CAAA;AAEtB;AAIA,SAAgB,KAAoB;CAClC,IAAM,CAAC,GAAc,KAAmB,EAAS,EAAK,GAChD,CAAC,GAAe,KAAoB,EAAkC,IAAI,GAC1E,CAAC,GAAQ,MAAa,EAAS,EAAE,GACjC,CAAC,GAAU,KAAe,EAAiB,KAAK,GAChD,CAAC,GAAc,KAAmB,EAAiB,KAAK,GACxD,CAAC,GAAM,KAAW,EAAkB,QAAQ,GAC5C,CAAC,GAAS,KAAc,EAAkC,IAAI,GAE9D,EAAE,SAAM,YAAS,gBAAY,EACjC,IACA,EAAE,aAAa,oBAAoB,CACrC,GAEM,IAAW,QAAc,GAAM,qBAAqB,CAAC,GAAG,CAAC,GAAM,iBAAiB,CAAC,GAEjF,IAAa,QAAc;EAC/B,IAAM,oBAAS,IAAI,IAAoB;EACvC,KAAK,IAAM,KAAK,GACd,KAAK,IAAM,KAAO,EAAE,MAAM,EAAO,IAAI,IAAM,EAAO,IAAI,CAAG,KAAK,KAAK,CAAC;EAEtE,OAAO,CAAC,GAAG,EAAO,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;CACzD,GAAG,CAAC,CAAQ,CAAC,GAEP,IAAW,QAAc;EAC7B,IAAM,IAAO,EAAO,KAAK,EAAE,YAAY;EAevC,OAAO,CAAC,GAdK,EAAS,QAAQ,MACxB,MAAa,SAAS,CAAC,EAAQ,KAAK,SAAS,CAAQ,KACrD,MAAiB,SAAS,EAAQ,WAAW,IAAqB,KACjE,IACY;GACf,EAAQ;GACR,EAAQ,WAAW;GACnB,EAAQ;GACR,EAAQ,KAAK,KAAK,GAAG;EACvB,EACG,KAAK,GAAG,EACR,YACI,EAAS,SAAS,CAAI,IATX,EAWT,CAAI,EAAE,MAAM,GAAG,MACxB,MAAS,UACL,EAAE,QAAQ,EAAE,QACZ,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CACtE;CACF,GAAG;EAAC;EAAU;EAAQ;EAAU;EAAc;CAAI,CAAC,GAE7C,IAAY,QAA4B;EAC5C,IAAM,IAAS,EAAS,QAAgC,GAAK,OAC3D,EAAI,EAAE,WAAW,EAAI,EAAE,WAAW,KAAK,GAChC,IACN,CAAC,CAAC;EACL,OAAO,OAAO,QAAQ,CAAM,EAAE,KAAK,CAAC,GAAQ,IAAQ,OAAO;GACzD,MAAM,EAAY,IAAS,SAAS;GACpC;GACA,OAAO,EAAY,CAAC;EACtB,EAAE;CACJ,GAAG,CAAC,CAAQ,CAAC,GAEP,IAAa,QAEf,CAAC,GAAG,CAAQ,EACT,QAAQ,MAAM,EAAE,QAAQ,CAAC,EACzB,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,CAAC,EACV,KAAK,OAAO;EAAE,MAAM,EAAE;EAAO,OAAO,EAAE;CAAM,EAAE,GACnD,CAAC,CAAQ,CACX,GAEM,IAAa,EAAc,GAAU;EACzC,UAAU;EACV,UAAU,GAAG,EAAO,GAAG,EAAS,GAAG,EAAa,GAAG;CACrD,CAAC;CAaD,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,MAAA;IACA,MAAM;IACN,OAAM;IACN,aAAY;IACZ,SACE,kBAAC,GAAD;KAAQ,WAAU;KAAmB,eAnBd;MAE7B,AADA,EAAiB,IAAI,GACrB,EAAgB,EAAI;KACtB;eAgBQ,CACE,kBAAC,GAAD,EAAM,WAAU,eAAgB,CAAA,GAAC,aAE3B;;GAEX,CAAA;GACD,kBAAC,GAAD,EAAA,UAAa,kLAGA,CAAA;GAEb,kBAAC,IAAD;IACE,MAAM;IACN,cAAc;IACd,SAAS;IACT,eAAe,KAAK,GAAQ;GAC7B,CAAA;GACD,kBAAC,IAAD;IACE,SAAS;IACT,eAAe,MAAS,CAAC,KAAQ,EAAW,IAAI;IAChD,SAlCqB,MAA8B;KAGvD,AAFA,EAAW,IAAI,GACf,EAAiB,CAAO,GACxB,EAAgB,EAAI;IACtB;GA+BK,CAAA;GAED,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,6EAA8E,CAAA,GAChG,kBAAC,GAAD;KACE,WAAU;KACV,aAAY;KACZ,OAAO;KACP,WAAW,MAAM,GAAU,EAAE,OAAO,KAAK;KACzC,cAAW;IACZ,CAAA,CACE;;GAEJ,EAAS,SAAS,MAAM,EAAU,SAAS,KAAK,EAAW,SAAS,KACnE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAW,OAAM;KAAoB,UAAS;eAC5C,kBAAC,GAAD;MACE,MAAM;MACN,QAAQ;MACR,aAAY;MACZ,aAAa,EAAS;MACtB,WAAU;KACX,CAAA;IACQ,CAAA,GACX,kBAAC,GAAD;KAAW,OAAM;KAAiB,UAAS;eACzC,kBAAC,GAAD;MACE,MAAM;MACN,MAAK;MACL,QAAQ,CAAC;OAAE,KAAK;OAAS,OAAO;MAAQ,CAAC;MACzC,aAAY;MACZ,QAAQ;MACR,eAAe;MACf,YAAW;MACX,WAAU;KACX,CAAA;IACQ,CAAA,CACR;QACH;GAEJ,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,SAAD;KAAO,WAAU;eAAjB;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;OAAY,CAAA,GACpE,kBAAC,GAAD;QAAQ,OAAO;QAAM,gBAAgB,MAAM,EAAQ,CAAY;kBAA/D,CACE,kBAAC,GAAD;SAAe,WAAU;mBACvB,kBAAC,GAAD,CAAc,CAAA;QACD,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAY,OAAM;mBAAS;QAA4B,CAAA,GACvD,kBAAC,GAAD;SAAY,OAAM;mBAAQ;QAAuB,CAAA,CACpC,EAAA,CAAA,CACT;SACL;;MAEL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAY,KAAK;QAChC,WAAW,EACT,6FACA,MAAa,QACT,yCACA,6CACN;kBARF,CASC,gBAEC,kBAAC,QAAD;SAAM,WAAU;mBAA+C,EAAS;QAAa,CAAA,CAC/E;WACP,EAAW,KAAK,CAAC,GAAK,OACrB,kBAAC,UAAD;QAEE,MAAK;QACL,eAAe,EAAY,CAAG;QAC9B,WAAW,EACT,qIACA,MAAa,IACT,yCACA,6CACN;kBATF,CAWE,kBAAC,QAAD;SAAM,WAAU;mBAAY;QAAU,CAAA,GACtC,kBAAC,QAAD;SAAM,WAAU;mBAAwC;QAAY,CAAA,CAC9D;UAZD,CAYC,CACT,CACE;;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAO,WAAU;mBAAU;QAAa,CAAA;QACxC,kBAAC,GAAD;SAAQ,OAAO;SAAc,eAAe;mBAA5C,CACE,kBAAC,GAAD;UAAe,WAAU;oBACvB,kBAAC,GAAD,CAAc,CAAA;SACD,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;UAAY,OAAM;oBAAM;SAAwB,CAAA,GAC/C,GAAiB,KAAK,MACrB,kBAAC,GAAD;UAAoB,OAAO;oBACxB;SACS,GAFK,CAEL,CACb,CACY,EAAA,CAAA,CACT;;QACR,kBAAC,GAAD;SAAO,WAAU;mBAAU;QAAW,CAAA;QACtC,kBAAC,GAAD;SAAQ,OAAO;SAAM,gBAAgB,MAAM,EAAQ,CAAY;mBAA/D,CACE,kBAAC,GAAD;UAAe,WAAU;oBACvB,kBAAC,GAAD,CAAc,CAAA;SACD,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;UAAY,OAAM;oBAAS;SAA4B,CAAA,GACvD,kBAAC,GAAD;UAAY,OAAM;oBAAQ;SAAuB,CAAA,CACpC,EAAA,CAAA,CACT;;OACL;;KACA;QAGP,kBAAC,OAAD,EAAA,UAAA;KACG,KAAW,EAAS,WAAW,IAC9B,kBAAC,OAAD;MAAK,WAAU;gBACZ,MAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,GAAG,MACjC,kBAAC,OAAD,EAEE,WAAU,8DACX,GAFM,CAEN,CACF;KACE,CAAA,IACH;KACH,CAAC,KAAW,EAAS,WAAW,IAC/B,kBAAC,GAAD;MACE,cACE,KAAU,MAAa,SAAS,MAAiB,QAC7C,iBACA;MAEN,OACE,KAAU,MAAa,SAAS,MAAiB,QAC7C,mCACA;MAEN,aACE,KAAU,MAAa,SAAS,MAAiB,QAC7C,8CACA;KAEP,CAAA,IACC;KACJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAW,UAAU,KAAK,MACzB,kBAAC,GAAD;OAA4B,WAAU;OAAQ,WAAU;iBACtD,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAW,CAAO;QACjC,WAAU;kBAHZ;SAKE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAY,QAAQ,EAAQ,OAAS,CAAA,GACpC,EAAQ,KAAK,MAAM,GAAG,CAAC,EAAE,KAAK,MAC7B,kBAAC,QAAD;WAEE,WAAU;qBAET;UACG,GAJC,CAID,CACP,CACE;;SACL,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAQ;SACP,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBACV,GAAU,CAAO;SACjB,CAAA;SACH,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAQ,YACP,EAAW,EAAQ,SAAS,IAE5B,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;WAEzB,CAAA,GACN,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAAuD,YAC5C,EAAW,EAAQ,SAAS,CACjC;aACH;cACL,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD,EAAK,WAAU,cAAe,CAAA,GAC7B,EAAQ,MAAM,eAAe,CAC1B;YACH;;QACC;;MACC,GA1CK,EAAQ,EA0Cb,CACZ;KACE,CAAA;KACL,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OACE,MAAM,EAAW;OACjB,WAAW,EAAW;OACtB,YAAY,EAAW;OACvB,UAAU,EAAW;OACrB,YAAY,EAAW;OACvB,SAAS,EAAW;OACpB,SAAS,EAAW;OACpB,QAAQ,EAAW;OACnB,QAAQ,EAAW;OACnB,WAAU;MACX,CAAA;KACE,CAAA;IACF,EAAA,CAAA,CACF;;EACF;;AAET"}
|
|
1
|
+
{"version":3,"file":"KnowledgeBasePage.js","names":[],"sources":["../../../src/assethandler/pages/KnowledgeBasePage.tsx"],"sourcesContent":["import { BookOpen, Plus, Search, Eye, User, Link2, ExternalLink, Pencil, X } from 'lucide-react';\nimport {\n Button,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Pagination,\n usePagination,\n SectionHeader,\n ChartCard,\n DonutChart,\n BarsChart,\n seriesColor,\n type DonutSlice,\n} from '../components';\nimport { useAssetHandlerNavigate } from '../hooks/useAssetHandlerNavigate';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { cn } from '../utils/cn';\nimport { notify } from '../utils/toast';\nimport { formatAssetOption, formatIncidentOption, parseTagsInput } from '../utils/knowledgeBase';\n\nconst KNOWLEDGE_ARTICLES_QUERY = gql`\n query AssetHandlerKnowledgeArticles {\n knowledgeArticles {\n id\n title\n slug\n summary\n content\n status\n tags\n views\n createdBy\n updatedBy\n publishedAt\n createdAt\n updatedAt\n relatedAssetId\n relatedIncidentId\n }\n }\n`;\n\nconst CREATE_KNOWLEDGE_ARTICLE_MUTATION = gql`\n mutation AssetHandlerCreateKnowledgeArticle($input: CreateKnowledgeArticleInput!) {\n createKnowledgeArticle(input: $input) {\n id\n title\n }\n }\n`;\n\nconst UPDATE_KNOWLEDGE_ARTICLE_MUTATION = gql`\n mutation AssetHandlerUpdateKnowledgeArticle($id: ID!, $input: UpdateKnowledgeArticleInput!) {\n updateKnowledgeArticle(id: $id, input: $input) {\n id\n title\n }\n }\n`;\n\nconst RECORD_KNOWLEDGE_ARTICLE_VIEW_MUTATION = gql`\n mutation AssetHandlerRecordKnowledgeArticleView($id: ID!) {\n recordKnowledgeArticleView(id: $id) {\n id\n views\n }\n }\n`;\n\ninterface KnowledgeArticle {\n id: string;\n title: string;\n slug: string;\n summary: string | null;\n content: string;\n status: string;\n tags: string[];\n views: number;\n createdBy: string;\n updatedBy: string;\n publishedAt: string | null;\n createdAt: string;\n updatedAt: string;\n relatedAssetId: string | null;\n relatedIncidentId: string | null;\n}\n\ninterface CreateKnowledgeArticleResult {\n createKnowledgeArticle: { id: string; title: string };\n}\n\ninterface UpdateKnowledgeArticleResult {\n updateKnowledgeArticle: { id: string; title: string };\n}\n\ninterface RecordKnowledgeArticleViewResult {\n recordKnowledgeArticleView: { id: string; views: number };\n}\n\nconst ARTICLE_STATUSES = ['DRAFT', 'PUBLISHED', 'ARCHIVED'] as const;\n\nconst STATUS_META: Record<string, { label: string; className: string }> = {\n PUBLISHED: {\n label: 'Published',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n },\n DRAFT: { label: 'Draft', className: 'bg-bg-sunken text-text-secondary' },\n ARCHIVED: {\n label: 'Archived',\n className: 'bg-status-warning-bg-subtle text-status-warning-text',\n },\n};\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\nconst formatDate = (value: string | null): string =>\n value\n ? new Date(value).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n : '—';\n\n/** A 2-line excerpt: the summary if present, otherwise the opening of the body. */\nfunction excerptOf(article: KnowledgeArticle): string {\n if (article.summary && article.summary.trim()) return article.summary.trim();\n const body = article.content.replace(/\\s+/g, ' ').trim();\n return body.length > 160 ? `${body.slice(0, 160)}…` : body;\n}\n\n/** Short avatar initials from an actor id (best-effort, no fabricated names). */\nfunction initialsOf(actor: string): string {\n const clean = actor.replace(/[^a-zA-Z0-9]/g, '');\n return (clean.slice(0, 2) || '?').toUpperCase();\n}\n\nfunction StatusPill({ status }: { status: string }) {\n const meta = STATUS_META[status] ?? {\n label: status,\n className: 'bg-bg-sunken text-text-secondary',\n };\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium',\n meta.className\n )}\n >\n {meta.label}\n </span>\n );\n}\n\nfunction ArticleDialog({\n open,\n onOpenChange,\n article,\n onSaved,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n article: KnowledgeArticle | null;\n onSaved: () => void;\n}) {\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-2xl\">\n {open && (\n <ArticleForm\n key={article?.id ?? 'new'}\n article={article}\n onOpenChange={onOpenChange}\n onSaved={onSaved}\n />\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nfunction ArticleForm({\n article,\n onOpenChange,\n onSaved,\n}: {\n article: KnowledgeArticle | null;\n onOpenChange: (open: boolean) => void;\n onSaved: () => void;\n}) {\n const isEdit = Boolean(article);\n const { assets, incidents } = useAssetHandlerStore();\n\n const [title, setTitle] = useState(article?.title ?? '');\n const [slug, setSlug] = useState(article?.slug ?? '');\n const [slugTouched, setSlugTouched] = useState(isEdit);\n const [summary, setSummary] = useState(article?.summary ?? '');\n const [content, setContent] = useState(article?.content ?? '');\n const [tags, setTags] = useState(article ? article.tags.join(', ') : '');\n const [status, setStatus] = useState<string>(article?.status ?? 'PUBLISHED');\n const [relatedAssetId, setRelatedAssetId] = useState(article?.relatedAssetId ?? '');\n const [relatedIncidentId, setRelatedIncidentId] = useState(article?.relatedIncidentId ?? '');\n\n const reset = () => {\n setTitle('');\n setSlug('');\n setSlugTouched(false);\n setSummary('');\n setContent('');\n setTags('');\n setStatus('PUBLISHED');\n setRelatedAssetId('');\n setRelatedIncidentId('');\n };\n\n const [createArticle, { loading: creating }] = useMutation<CreateKnowledgeArticleResult>(\n CREATE_KNOWLEDGE_ARTICLE_MUTATION,\n {\n onCompleted: (data) => {\n notify.success(\n 'Article published',\n `${data.createKnowledgeArticle.title} is now available.`\n );\n reset();\n onOpenChange(false);\n onSaved();\n },\n onError: (error) => notify.error('Could not publish article', error.message),\n }\n );\n\n const [updateArticle, { loading: updating }] = useMutation<UpdateKnowledgeArticleResult>(\n UPDATE_KNOWLEDGE_ARTICLE_MUTATION,\n {\n refetchQueries: ['AssetHandlerKnowledgeArticles'],\n onCompleted: (data) => {\n notify.success('Article updated', `${data.updateKnowledgeArticle.title} has been saved.`);\n onOpenChange(false);\n onSaved();\n },\n onError: (error) => notify.error('Could not update article', error.message),\n }\n );\n\n const selectedAsset = useMemo(\n () => assets.find((a) => a.id === relatedAssetId),\n [assets, relatedAssetId]\n );\n const selectedIncident = useMemo(\n () => incidents.find((i) => i.id === relatedIncidentId),\n [incidents, relatedIncidentId]\n );\n\n const handleTitleChange = (next: string) => {\n setTitle(next);\n if (!slugTouched) setSlug(slugify(next));\n };\n\n const handleSubmit = () => {\n if (!title.trim() || !slug.trim()) {\n notify.warning('Missing details', 'Title and slug are required.');\n return;\n }\n if (!content.trim()) {\n notify.warning('Body required', 'Write the article body before publishing.');\n return;\n }\n\n const parsedTags = parseTagsInput(tags);\n const baseInput = {\n title: title.trim(),\n summary: summary.trim() || null,\n content: content.trim(),\n tags: parsedTags,\n status,\n relatedAssetId: relatedAssetId || null,\n relatedIncidentId: relatedIncidentId || null,\n };\n\n if (isEdit && article) {\n void updateArticle({\n variables: { id: article.id, input: baseInput },\n });\n } else {\n void createArticle({\n variables: {\n input: { ...baseInput, slug: slug.trim() },\n },\n });\n }\n };\n\n return (\n <>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{isEdit ? 'Edit article' : 'New Article'}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {isEdit\n ? 'Update the runbook or fix so the team has the latest guidance.'\n : 'Capture a runbook or fix so your team can find it later.'}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Title *</Label>\n <Input value={title} onChange={(e) => handleTitleChange(e.target.value)} />\n </div>\n <div className=\"space-y-2\">\n <Label>Slug *</Label>\n <Input\n value={slug}\n disabled={isEdit}\n onChange={(e) => {\n setSlugTouched(true);\n setSlug(e.target.value);\n }}\n />\n </div>\n </div>\n <div className=\"space-y-2\">\n <Label>Summary</Label>\n <Input\n placeholder=\"One-line description\"\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Body *</Label>\n <Textarea\n placeholder=\"Write the full article body — steps, context, links…\"\n value={content}\n onChange={(e) => setContent(e.target.value)}\n rows={8}\n />\n </div>\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Category tags</Label>\n <Input\n placeholder=\"Runbooks, Fixes, Procedures…\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Status</Label>\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ARTICLE_STATUSES.map((s) => (\n <SelectItem key={s} value={s}>\n {s}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Related asset</Label>\n <Select value={relatedAssetId} onValueChange={setRelatedAssetId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">None</SelectItem>\n {assets.map((asset) => (\n <SelectItem key={asset.id} value={asset.id}>\n {formatAssetOption(asset)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {selectedAsset ? (\n <div className=\"flex flex-wrap gap-2\">\n <span className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary\">\n {formatAssetOption(selectedAsset)}\n <button\n type=\"button\"\n onClick={() => setRelatedAssetId('')}\n className=\"rounded-full p-0.5 hover:bg-action-primary-bg/20\"\n aria-label=\"Clear related asset\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n </div>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <Label>Related incident</Label>\n <Select value={relatedIncidentId} onValueChange={setRelatedIncidentId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select an incident\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">None</SelectItem>\n {incidents.map((incident) => (\n <SelectItem key={incident.id} value={incident.id}>\n {formatIncidentOption(incident)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {selectedIncident ? (\n <div className=\"flex flex-wrap gap-2\">\n <span className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary\">\n {formatIncidentOption(selectedIncident)}\n <button\n type=\"button\"\n onClick={() => setRelatedIncidentId('')}\n className=\"rounded-full p-0.5 hover:bg-action-primary-bg/20\"\n aria-label=\"Clear related incident\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n </div>\n ) : null}\n </div>\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={creating || updating}\n >\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={creating || updating}>\n {isEdit\n ? updating\n ? 'Saving…'\n : 'Save changes'\n : creating\n ? 'Publishing…'\n : 'Publish article'}\n </Button>\n </ResponsiveDialogFooter>\n </>\n );\n}\n\nfunction ArticleReader({\n article,\n onOpenChange,\n onEdit,\n}: {\n article: KnowledgeArticle | null;\n onOpenChange: (open: boolean) => void;\n onEdit: (article: KnowledgeArticle) => void;\n}) {\n const navigate = useAssetHandlerNavigate();\n return (\n <ResponsiveDialog open={Boolean(article)} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-2xl\">\n {article ? (\n <>\n <ResponsiveDialogHeader>\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusPill status={article.status} />\n {article.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary\"\n >\n {tag}\n </span>\n ))}\n </div>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0 flex-1\">\n <ResponsiveDialogTitle className=\"mt-1\">{article.title}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Updated {formatDate(article.updatedAt)} · {article.views.toLocaleString()} views\n </ResponsiveDialogDescription>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n onOpenChange(false);\n onEdit(article);\n }}\n >\n <Pencil className=\"mr-1.5 h-3.5 w-3.5\" />\n Edit\n </Button>\n </div>\n </ResponsiveDialogHeader>\n <div className=\"whitespace-pre-wrap text-sm leading-relaxed text-text-primary\">\n {article.content}\n </div>\n {article.relatedAssetId || article.relatedIncidentId ? (\n <div className=\"mt-4 space-y-2 border-t border-border-seam pt-4\">\n <h4 className=\"flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n <Link2 className=\"h-3.5 w-3.5\" />\n Linked records\n </h4>\n <div className=\"flex flex-wrap gap-2\">\n {article.relatedAssetId ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n onOpenChange(false);\n navigate(`/assets/${article.relatedAssetId}`);\n }}\n >\n View linked asset\n <ExternalLink className=\"ml-1.5 h-3.5 w-3.5\" />\n </Button>\n ) : null}\n {article.relatedIncidentId ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n onOpenChange(false);\n navigate(`/itil/incidents/${article.relatedIncidentId}`);\n }}\n >\n View linked incident\n <ExternalLink className=\"ml-1.5 h-3.5 w-3.5\" />\n </Button>\n ) : null}\n </div>\n </div>\n ) : null}\n </>\n ) : null}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\ntype SortKey = 'recent' | 'views';\n\nexport function KnowledgeBasePage() {\n const [isDialogOpen, setIsDialogOpen] = useState(false);\n const [dialogArticle, setDialogArticle] = useState<KnowledgeArticle | null>(null);\n const [search, setSearch] = useState('');\n const [category, setCategory] = useState<string>('ALL');\n const [statusFilter, setStatusFilter] = useState<string>('ALL');\n const [sort, setSort] = useState<SortKey>('recent');\n const [reading, setReading] = useState<KnowledgeArticle | null>(null);\n\n const { data, loading, refetch } = useQuery<{ knowledgeArticles: KnowledgeArticle[] }>(\n KNOWLEDGE_ARTICLES_QUERY,\n { fetchPolicy: 'cache-and-network' }\n );\n const [recordKnowledgeArticleView] = useMutation<RecordKnowledgeArticleViewResult>(\n RECORD_KNOWLEDGE_ARTICLE_VIEW_MUTATION\n );\n\n const articles = useMemo(() => data?.knowledgeArticles ?? [], [data?.knowledgeArticles]);\n\n const categories = useMemo(() => {\n const counts = new Map<string, number>();\n for (const a of articles) {\n for (const tag of a.tags) counts.set(tag, (counts.get(tag) ?? 0) + 1);\n }\n return [...counts.entries()].sort((a, b) => b[1] - a[1]);\n }, [articles]);\n\n const filtered = useMemo(() => {\n const term = search.trim().toLowerCase();\n const list = articles.filter((article) => {\n if (category !== 'ALL' && !article.tags.includes(category)) return false;\n if (statusFilter !== 'ALL' && article.status !== statusFilter) return false;\n if (!term) return true;\n const haystack = [\n article.title,\n article.summary ?? '',\n article.content,\n article.tags.join(' '),\n ]\n .join(' ')\n .toLowerCase();\n return haystack.includes(term);\n });\n return [...list].sort((a, b) =>\n sort === 'views'\n ? b.views - a.views\n : new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n );\n }, [articles, search, category, statusFilter, sort]);\n\n const statusMix = useMemo<DonutSlice[]>(() => {\n const counts = articles.reduce<Record<string, number>>((acc, a) => {\n acc[a.status] = (acc[a.status] ?? 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts).map(([status, value], i) => ({\n name: STATUS_META[status]?.label ?? status,\n value,\n color: seriesColor(i),\n }));\n }, [articles]);\n\n const topByViews = useMemo(\n () =>\n [...articles]\n .filter((a) => a.views > 0)\n .sort((a, b) => b.views - a.views)\n .slice(0, 6)\n .map((a) => ({ name: a.title, views: a.views })),\n [articles]\n );\n\n const pagination = usePagination(filtered, {\n pageSize: 9,\n resetKey: `${search}|${category}|${statusFilter}|${sort}`,\n });\n\n const handleNewArticle = () => {\n setDialogArticle(null);\n setIsDialogOpen(true);\n };\n\n const handleEditArticle = (article: KnowledgeArticle) => {\n setReading(null);\n setDialogArticle(article);\n setIsDialogOpen(true);\n };\n\n const handleReadArticle = (article: KnowledgeArticle) => {\n setReading(article);\n void recordKnowledgeArticleView({ variables: { id: article.id } })\n .then((result) => {\n const recorded = result.data?.recordKnowledgeArticleView;\n if (!recorded) return;\n setReading((current) =>\n current?.id === recorded.id ? { ...current, views: recorded.views } : current\n );\n void refetch();\n })\n .catch((error: unknown) => {\n notify.error(\n 'Could not record article view',\n error instanceof Error ? error.message : 'The article is still available to read.'\n );\n });\n };\n\n return (\n <div className=\"space-y-5 p-4 sm:p-6\">\n <SectionHeader\n hero\n icon={BookOpen}\n title=\"Knowledge Base\"\n description=\"Turn one-off fixes and runbooks into repeatable, searchable operational knowledge.\"\n actions={\n <Button className=\"w-full sm:w-auto\" onClick={handleNewArticle}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New Article\n </Button>\n }\n />\n <PagePurpose>\n Capture the fixes and runbooks your team relies on so the next person doesn't start\n from scratch. Link articles to incidents and assets, then search them when troubleshooting.\n </PagePurpose>\n\n <ArticleDialog\n open={isDialogOpen}\n onOpenChange={setIsDialogOpen}\n article={dialogArticle}\n onSaved={() => void refetch()}\n />\n <ArticleReader\n article={reading}\n onOpenChange={(open) => !open && setReading(null)}\n onEdit={handleEditArticle}\n />\n\n <div className=\"relative\">\n <Search className=\"absolute left-3.5 top-1/2 h-4.5 w-4.5 -translate-y-1/2 text-text-secondary\" />\n <Input\n className=\"h-12 pl-11 text-base\"\n placeholder=\"Search runbooks, fixes and procedures…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n aria-label=\"Search knowledge articles\"\n />\n </div>\n\n {articles.length > 0 && (statusMix.length > 0 || topByViews.length > 0) ? (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <ChartCard title=\"Publishing status\" subtitle=\"Articles by status\">\n <DonutChart\n data={statusMix}\n height={200}\n centerLabel=\"Articles\"\n centerValue={articles.length}\n ariaLabel=\"Articles by status\"\n />\n </ChartCard>\n <ChartCard title=\"Most consulted\" subtitle=\"Top articles by views\">\n <BarsChart\n data={topByViews}\n xKey=\"name\"\n series={[{ key: 'views', label: 'Views' }]}\n orientation=\"horizontal\"\n height={200}\n categoryWidth={150}\n emptyLabel=\"No views recorded yet\"\n ariaLabel=\"Top articles by views\"\n />\n </ChartCard>\n </div>\n ) : null}\n\n <div className=\"grid gap-5 lg:grid-cols-[200px_1fr]\">\n {/* Category facet rail */}\n <aside className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Category</h3>\n <Select value={sort} onValueChange={(v) => setSort(v as SortKey)}>\n <SelectTrigger className=\"h-8 w-[130px] text-xs lg:hidden\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"recent\">Recently updated</SelectItem>\n <SelectItem value=\"views\">Most viewed</SelectItem>\n </SelectContent>\n </Select>\n </div>\n {/* Mobile: horizontal chips; desktop: vertical list */}\n <div className=\"flex gap-2 overflow-x-auto pb-1 lg:flex-col lg:overflow-visible lg:pb-0\">\n <button\n type=\"button\"\n onClick={() => setCategory('ALL')}\n className={cn(\n 'shrink-0 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full',\n category === 'ALL'\n ? 'bg-action-primary-bg/10 text-primary'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n All articles\n <span className=\"ml-1.5 text-xs text-text-muted tabular-nums\">{articles.length}</span>\n </button>\n {categories.map(([tag, count]) => (\n <button\n key={tag}\n type=\"button\"\n onClick={() => setCategory(tag)}\n className={cn(\n 'flex shrink-0 items-center justify-between gap-2 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full',\n category === tag\n ? 'bg-action-primary-bg/10 text-primary'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n <span className=\"truncate\">{tag}</span>\n <span className=\"text-xs text-text-muted tabular-nums\">{count}</span>\n </button>\n ))}\n </div>\n <div className=\"hidden space-y-1.5 border-t border-border-seam pt-3 lg:block\">\n <Label className=\"text-xs\">Status</Label>\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger className=\"h-9\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All statuses</SelectItem>\n {ARTICLE_STATUSES.map((s) => (\n <SelectItem key={s} value={s}>\n {s}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Label className=\"text-xs\">Sort</Label>\n <Select value={sort} onValueChange={(v) => setSort(v as SortKey)}>\n <SelectTrigger className=\"h-9\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"recent\">Recently updated</SelectItem>\n <SelectItem value=\"views\">Most viewed</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </aside>\n\n {/* Article grid */}\n <div>\n {loading && articles.length === 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div\n key={i}\n className=\"h-40 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : null}\n {!loading && filtered.length === 0 ? (\n <IllustratedEmptyState\n illustration={\n search || category !== 'ALL' || statusFilter !== 'ALL'\n ? 'empty-search'\n : 'empty-data'\n }\n title={\n search || category !== 'ALL' || statusFilter !== 'ALL'\n ? 'No articles match your filters'\n : 'No knowledge articles found'\n }\n description={\n search || category !== 'ALL' || statusFilter !== 'ALL'\n ? 'Try a different term, category or status.'\n : 'Publish an article to start building your knowledge base.'\n }\n />\n ) : null}\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {pagination.pageItems.map((article) => (\n <GlassCard key={article.id} treatment=\"glass\" className=\"border-border-seam\">\n <button\n type=\"button\"\n onClick={() => handleReadArticle(article)}\n className=\"flex h-full w-full flex-col gap-3 p-4 text-left\"\n >\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusPill status={article.status} />\n {article.tags.slice(0, 1).map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h2 className=\"line-clamp-2 text-base font-semibold text-text-primary\">\n {article.title}\n </h2>\n <p className=\"line-clamp-2 flex-1 text-sm text-text-secondary\">\n {excerptOf(article)}\n </p>\n <div className=\"flex items-center justify-between gap-2 border-t border-border-seam/60 pt-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-[10px] font-semibold text-text-secondary\">\n {article.updatedBy ? (\n initialsOf(article.updatedBy)\n ) : (\n <User className=\"h-3 w-3\" />\n )}\n </span>\n <span className=\"truncate text-xs text-text-secondary\">\n Updated {formatDate(article.updatedAt)}\n </span>\n </div>\n <span className=\"flex shrink-0 items-center gap-1 text-xs text-text-muted tabular-nums\">\n <Eye className=\"h-3.5 w-3.5\" />\n {article.views.toLocaleString()}\n </span>\n </div>\n </button>\n </GlassCard>\n ))}\n </div>\n <div className=\"mt-4\">\n <Pagination\n page={pagination.page}\n pageCount={pagination.pageCount}\n rangeStart={pagination.rangeStart}\n rangeEnd={pagination.rangeEnd}\n totalItems={pagination.totalItems}\n canPrev={pagination.canPrev}\n canNext={pagination.canNext}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"articles\"\n />\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|