@burdenoff/microfe-assethandler 2026.820.1 → 2026.820.3

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":"AAkYA,wBAAgB,iBAAiB,gCAmThC"}
1
+ {"version":3,"file":"KnowledgeBasePage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/KnowledgeBasePage.tsx"],"names":[],"mappings":"AA6iBA,wBAAgB,iBAAiB,gCAoUhC"}
@@ -1,21 +1,23 @@
1
- import { seriesColor as e } from "../components/charts/chartTheme.js";
2
- import { useAssetHandlerNavigate as t } from "../hooks/useAssetHandlerNavigate.js";
3
- import { cn as n } from "../utils/cn.js";
4
- import { notify as r } from "../utils/toast.js";
5
- import { BarsChart as i } from "../components/charts/BarsChart.js";
6
- import { DonutChart as a } from "../components/charts/DonutChart.js";
7
- import { Pagination as o, usePagination as s } from "../components/Pagination.js";
8
- import { ChartCard as c } from "../components/ChartCard.js";
9
- import { SectionHeader as l } from "../components/SectionHeader.js";
1
+ import { useAssetHandlerStore as e } from "../store/assethandlerStore.js";
2
+ import { seriesColor as t } from "../components/charts/chartTheme.js";
3
+ import { useAssetHandlerNavigate as n } from "../hooks/useAssetHandlerNavigate.js";
4
+ import { cn as r } from "../utils/cn.js";
5
+ import { notify as i } from "../utils/toast.js";
6
+ import { BarsChart as a } from "../components/charts/BarsChart.js";
7
+ import { DonutChart as o } from "../components/charts/DonutChart.js";
8
+ import { Pagination as s, usePagination as c } from "../components/Pagination.js";
9
+ import { ChartCard as l } from "../components/ChartCard.js";
10
+ import { SectionHeader as u } from "../components/SectionHeader.js";
10
11
  import "../components/index.js";
11
- import { useMemo as u, useState as d } from "react";
12
- import { BookOpen as f, ExternalLink as p, Eye as m, Link2 as h, Plus as g, Search as _, User as v } from "lucide-react";
13
- import { gql as y } from "@apollo/client";
14
- import { useMutation as b, useQuery as x } from "@apollo/client/react";
15
- import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
16
- import { Button as T, GlassCard as E, IllustratedEmptyState as D, Input as O, Label as k, PagePurpose as ee, ResponsiveDialog as A, ResponsiveDialogContent as j, ResponsiveDialogDescription as M, ResponsiveDialogFooter as N, ResponsiveDialogHeader as P, ResponsiveDialogTitle as F, Select as I, SelectContent as L, SelectItem as R, SelectTrigger as z, SelectValue as B, Textarea as V } from "@burdenoff/fe-libs/ui";
12
+ import { formatAssetOption as d, formatIncidentOption as f, parseTagsInput as ee } from "../utils/knowledgeBase.js";
13
+ import { useMemo as p, useState as m } from "react";
14
+ import { BookOpen as h, ExternalLink as g, Eye as _, Link2 as v, Pencil as y, Plus as b, Search as x, User as S, X as C } from "lucide-react";
15
+ import { gql as w } from "@apollo/client";
16
+ import { useMutation as T, useQuery as E } from "@apollo/client/react";
17
+ import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";
18
+ import { Button as A, GlassCard as j, IllustratedEmptyState as M, Input as N, Label as P, PagePurpose as F, ResponsiveDialog as I, ResponsiveDialogContent as L, ResponsiveDialogDescription as R, ResponsiveDialogFooter as te, ResponsiveDialogHeader as z, ResponsiveDialogTitle as B, Select as V, SelectContent as H, SelectItem as U, SelectTrigger as W, SelectValue as G, Textarea as K } from "@burdenoff/fe-libs/ui";
17
19
  //#region src/assethandler/pages/KnowledgeBasePage.tsx
18
- var H = y`
20
+ var ne = w`
19
21
  query AssetHandlerKnowledgeArticles {
20
22
  knowledgeArticles {
21
23
  id
@@ -35,18 +37,25 @@ var H = y`
35
37
  relatedIncidentId
36
38
  }
37
39
  }
38
- `, U = y`
40
+ `, q = w`
39
41
  mutation AssetHandlerCreateKnowledgeArticle($input: CreateKnowledgeArticleInput!) {
40
42
  createKnowledgeArticle(input: $input) {
41
43
  id
42
44
  title
43
45
  }
44
46
  }
45
- `, W = [
47
+ `, J = w`
48
+ mutation AssetHandlerUpdateKnowledgeArticle($id: ID!, $input: UpdateKnowledgeArticleInput!) {
49
+ updateKnowledgeArticle(id: $id, input: $input) {
50
+ id
51
+ title
52
+ }
53
+ }
54
+ `, re = [
46
55
  "DRAFT",
47
56
  "PUBLISHED",
48
57
  "ARCHIVED"
49
- ], G = {
58
+ ], Y = {
50
59
  PUBLISHED: {
51
60
  label: "Published",
52
61
  className: "bg-status-success-bg-subtle text-status-success-text"
@@ -59,193 +68,285 @@ var H = y`
59
68
  label: "Archived",
60
69
  className: "bg-status-warning-bg-subtle text-status-warning-text"
61
70
  }
62
- }, K = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), q = (e) => e ? new Date(e).toLocaleDateString(void 0, {
71
+ }, X = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), Z = (e) => e ? new Date(e).toLocaleDateString(void 0, {
63
72
  year: "numeric",
64
73
  month: "short",
65
74
  day: "numeric"
66
75
  }) : "—";
67
- function te(e) {
76
+ function ie(e) {
68
77
  if (e.summary && e.summary.trim()) return e.summary.trim();
69
78
  let t = e.content.replace(/\s+/g, " ").trim();
70
79
  return t.length > 160 ? `${t.slice(0, 160)}…` : t;
71
80
  }
72
- function ne(e) {
81
+ function Q(e) {
73
82
  return (e.replace(/[^a-zA-Z0-9]/g, "").slice(0, 2) || "?").toUpperCase();
74
83
  }
75
- function J({ status: e }) {
76
- let t = G[e] ?? {
84
+ function $({ status: e }) {
85
+ let t = Y[e] ?? {
77
86
  label: e,
78
87
  className: "bg-bg-sunken text-text-secondary"
79
88
  };
80
- return /* @__PURE__ */ C("span", {
81
- className: n("inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium", t.className),
89
+ return /* @__PURE__ */ O("span", {
90
+ className: r("inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium", t.className),
82
91
  children: t.label
83
92
  });
84
93
  }
85
- function re({ open: e, onOpenChange: t, onPublished: n }) {
86
- let [i, a] = d(""), [o, s] = d(""), [c, l] = d(!1), [u, f] = d(""), [p, m] = d(""), [h, g] = d(""), [_, v] = d("PUBLISHED"), y = () => {
87
- a(""), s(""), l(!1), f(""), m(""), g(""), v("PUBLISHED");
88
- }, [x, { loading: S }] = b(U, {
89
- onCompleted: (e) => {
90
- r.success("Article published", `${e.createKnowledgeArticle.title} is now available.`), y(), t(!1), n();
91
- },
92
- onError: (e) => r.error("Could not publish article", e.message)
93
- }), E = (e) => {
94
- a(e), c || s(K(e));
95
- };
96
- return /* @__PURE__ */ C(A, {
94
+ function ae({ open: e, onOpenChange: t, article: n, onSaved: r }) {
95
+ return /* @__PURE__ */ O(I, {
97
96
  open: e,
98
97
  onOpenChange: t,
99
- children: /* @__PURE__ */ w(j, {
98
+ children: /* @__PURE__ */ O(L, {
100
99
  className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl",
100
+ children: e && /* @__PURE__ */ O(oe, {
101
+ article: n,
102
+ onOpenChange: t,
103
+ onSaved: r
104
+ }, n?.id ?? "new")
105
+ })
106
+ });
107
+ }
108
+ function oe({ article: t, onOpenChange: n, onSaved: r }) {
109
+ let a = !!t, { assets: o, incidents: s } = e(), [c, l] = m(t?.title ?? ""), [u, h] = m(t?.slug ?? ""), [g, _] = m(a), [v, y] = m(t?.summary ?? ""), [b, x] = m(t?.content ?? ""), [S, w] = m(t ? t.tags.join(", ") : ""), [E, j] = m(t?.status ?? "PUBLISHED"), [M, F] = m(t?.relatedAssetId ?? ""), [I, L] = m(t?.relatedIncidentId ?? ""), ne = () => {
110
+ l(""), h(""), _(!1), y(""), x(""), w(""), j("PUBLISHED"), F(""), L("");
111
+ }, [Y, { loading: Z }] = T(q, {
112
+ onCompleted: (e) => {
113
+ i.success("Article published", `${e.createKnowledgeArticle.title} is now available.`), ne(), n(!1), r();
114
+ },
115
+ onError: (e) => i.error("Could not publish article", e.message)
116
+ }), [ie, { loading: Q }] = T(J, {
117
+ refetchQueries: ["AssetHandlerKnowledgeArticles"],
118
+ onCompleted: (e) => {
119
+ i.success("Article updated", `${e.updateKnowledgeArticle.title} has been saved.`), n(!1), r();
120
+ },
121
+ onError: (e) => i.error("Could not update article", e.message)
122
+ }), $ = p(() => o.find((e) => e.id === M), [o, M]), ae = p(() => s.find((e) => e.id === I), [s, I]), oe = (e) => {
123
+ l(e), g || h(X(e));
124
+ };
125
+ return /* @__PURE__ */ k(D, { children: [
126
+ /* @__PURE__ */ k(z, { children: [/* @__PURE__ */ O(B, { children: a ? "Edit article" : "New Article" }), /* @__PURE__ */ O(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." })] }),
127
+ /* @__PURE__ */ k("div", {
128
+ className: "space-y-4",
101
129
  children: [
102
- /* @__PURE__ */ w(P, { children: [/* @__PURE__ */ C(F, { children: "New Article" }), /* @__PURE__ */ C(M, { children: "Capture a runbook or fix so your team can find it later." })] }),
103
- /* @__PURE__ */ w("div", {
104
- className: "space-y-4",
105
- children: [
106
- /* @__PURE__ */ w("div", {
107
- className: "grid gap-4 md:grid-cols-2",
108
- children: [/* @__PURE__ */ w("div", {
109
- className: "space-y-2",
110
- children: [/* @__PURE__ */ C(k, { children: "Title *" }), /* @__PURE__ */ C(O, {
111
- value: i,
112
- onChange: (e) => E(e.target.value)
113
- })]
114
- }), /* @__PURE__ */ w("div", {
115
- className: "space-y-2",
116
- children: [/* @__PURE__ */ C(k, { children: "Slug *" }), /* @__PURE__ */ C(O, {
117
- value: o,
118
- onChange: (e) => {
119
- l(!0), s(e.target.value);
120
- }
121
- })]
122
- })]
123
- }),
124
- /* @__PURE__ */ w("div", {
125
- className: "space-y-2",
126
- children: [/* @__PURE__ */ C(k, { children: "Summary" }), /* @__PURE__ */ C(O, {
127
- placeholder: "One-line description",
128
- value: u,
129
- onChange: (e) => f(e.target.value)
130
- })]
131
- }),
132
- /* @__PURE__ */ w("div", {
133
- className: "space-y-2",
134
- children: [/* @__PURE__ */ C(k, { children: "Body *" }), /* @__PURE__ */ C(V, {
135
- placeholder: "Write the full article body — steps, context, links…",
136
- value: p,
137
- onChange: (e) => m(e.target.value),
138
- rows: 8
139
- })]
140
- }),
141
- /* @__PURE__ */ w("div", {
142
- className: "grid gap-4 md:grid-cols-2",
143
- children: [/* @__PURE__ */ w("div", {
144
- className: "space-y-2",
145
- children: [/* @__PURE__ */ C(k, { children: "Category tags" }), /* @__PURE__ */ C(O, {
146
- placeholder: "Runbooks, Fixes, Procedures…",
147
- value: h,
148
- onChange: (e) => g(e.target.value)
149
- })]
150
- }), /* @__PURE__ */ w("div", {
151
- className: "space-y-2",
152
- children: [/* @__PURE__ */ C(k, { children: "Status" }), /* @__PURE__ */ w(I, {
153
- value: _,
154
- onValueChange: (e) => v(e),
155
- children: [/* @__PURE__ */ C(z, { children: /* @__PURE__ */ C(B, {}) }), /* @__PURE__ */ C(L, { children: W.map((e) => /* @__PURE__ */ C(R, {
156
- value: e,
157
- children: e
158
- }, e)) })]
159
- })]
160
- })]
161
- })
162
- ]
130
+ /* @__PURE__ */ k("div", {
131
+ className: "grid gap-4 md:grid-cols-2",
132
+ children: [/* @__PURE__ */ k("div", {
133
+ className: "space-y-2",
134
+ children: [/* @__PURE__ */ O(P, { children: "Title *" }), /* @__PURE__ */ O(N, {
135
+ value: c,
136
+ onChange: (e) => oe(e.target.value)
137
+ })]
138
+ }), /* @__PURE__ */ k("div", {
139
+ className: "space-y-2",
140
+ children: [/* @__PURE__ */ O(P, { children: "Slug *" }), /* @__PURE__ */ O(N, {
141
+ value: u,
142
+ disabled: a,
143
+ onChange: (e) => {
144
+ _(!0), h(e.target.value);
145
+ }
146
+ })]
147
+ })]
163
148
  }),
164
- /* @__PURE__ */ w(N, { children: [/* @__PURE__ */ C(T, {
165
- variant: "outline",
166
- onClick: () => t(!1),
167
- disabled: S,
168
- children: "Cancel"
169
- }), /* @__PURE__ */ C(T, {
170
- onClick: () => {
171
- if (!i.trim() || !o.trim()) {
172
- r.warning("Missing details", "Title and slug are required.");
173
- return;
174
- }
175
- if (!p.trim()) {
176
- r.warning("Body required", "Write the article body before publishing.");
177
- return;
178
- }
179
- let e = h.split(",").map((e) => e.trim()).filter((e) => e.length > 0);
180
- x({ variables: { input: {
181
- title: i.trim(),
182
- slug: o.trim(),
183
- summary: u.trim() || null,
184
- content: p.trim(),
185
- tags: e,
186
- status: _
187
- } } });
188
- },
189
- disabled: S,
190
- children: S ? "Publishing…" : "Publish article"
191
- })] })
149
+ /* @__PURE__ */ k("div", {
150
+ className: "space-y-2",
151
+ children: [/* @__PURE__ */ O(P, { children: "Summary" }), /* @__PURE__ */ O(N, {
152
+ placeholder: "One-line description",
153
+ value: v,
154
+ onChange: (e) => y(e.target.value)
155
+ })]
156
+ }),
157
+ /* @__PURE__ */ k("div", {
158
+ className: "space-y-2",
159
+ children: [/* @__PURE__ */ O(P, { children: "Body *" }), /* @__PURE__ */ O(K, {
160
+ placeholder: "Write the full article body — steps, context, links…",
161
+ value: b,
162
+ onChange: (e) => x(e.target.value),
163
+ rows: 8
164
+ })]
165
+ }),
166
+ /* @__PURE__ */ k("div", {
167
+ className: "grid gap-4 md:grid-cols-2",
168
+ children: [/* @__PURE__ */ k("div", {
169
+ className: "space-y-2",
170
+ children: [/* @__PURE__ */ O(P, { children: "Category tags" }), /* @__PURE__ */ O(N, {
171
+ placeholder: "Runbooks, Fixes, Procedures…",
172
+ value: S,
173
+ onChange: (e) => w(e.target.value)
174
+ })]
175
+ }), /* @__PURE__ */ k("div", {
176
+ className: "space-y-2",
177
+ children: [/* @__PURE__ */ O(P, { children: "Status" }), /* @__PURE__ */ k(V, {
178
+ value: E,
179
+ onValueChange: j,
180
+ children: [/* @__PURE__ */ O(W, { children: /* @__PURE__ */ O(G, {}) }), /* @__PURE__ */ O(H, { children: re.map((e) => /* @__PURE__ */ O(U, {
181
+ value: e,
182
+ children: e
183
+ }, e)) })]
184
+ })]
185
+ })]
186
+ }),
187
+ /* @__PURE__ */ k("div", {
188
+ className: "grid gap-4 md:grid-cols-2",
189
+ children: [/* @__PURE__ */ k("div", {
190
+ className: "space-y-2",
191
+ children: [
192
+ /* @__PURE__ */ O(P, { children: "Related asset" }),
193
+ /* @__PURE__ */ k(V, {
194
+ value: M,
195
+ onValueChange: F,
196
+ children: [/* @__PURE__ */ O(W, { children: /* @__PURE__ */ O(G, { placeholder: "Select an asset" }) }), /* @__PURE__ */ k(H, { children: [/* @__PURE__ */ O(U, {
197
+ value: "",
198
+ children: "None"
199
+ }), o.map((e) => /* @__PURE__ */ O(U, {
200
+ value: e.id,
201
+ children: d(e)
202
+ }, e.id))] })]
203
+ }),
204
+ $ ? /* @__PURE__ */ O("div", {
205
+ className: "flex flex-wrap gap-2",
206
+ children: /* @__PURE__ */ k("span", {
207
+ 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($), /* @__PURE__ */ O("button", {
209
+ type: "button",
210
+ onClick: () => F(""),
211
+ className: "rounded-full p-0.5 hover:bg-action-primary-bg/20",
212
+ "aria-label": "Clear related asset",
213
+ children: /* @__PURE__ */ O(C, { className: "h-3 w-3" })
214
+ })]
215
+ })
216
+ }) : null
217
+ ]
218
+ }), /* @__PURE__ */ k("div", {
219
+ className: "space-y-2",
220
+ children: [
221
+ /* @__PURE__ */ O(P, { children: "Related incident" }),
222
+ /* @__PURE__ */ k(V, {
223
+ value: I,
224
+ onValueChange: L,
225
+ children: [/* @__PURE__ */ O(W, { children: /* @__PURE__ */ O(G, { placeholder: "Select an incident" }) }), /* @__PURE__ */ k(H, { children: [/* @__PURE__ */ O(U, {
226
+ value: "",
227
+ children: "None"
228
+ }), s.map((e) => /* @__PURE__ */ O(U, {
229
+ value: e.id,
230
+ children: f(e)
231
+ }, e.id))] })]
232
+ }),
233
+ ae ? /* @__PURE__ */ O("div", {
234
+ className: "flex flex-wrap gap-2",
235
+ children: /* @__PURE__ */ k("span", {
236
+ 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(ae), /* @__PURE__ */ O("button", {
238
+ type: "button",
239
+ onClick: () => L(""),
240
+ className: "rounded-full p-0.5 hover:bg-action-primary-bg/20",
241
+ "aria-label": "Clear related incident",
242
+ children: /* @__PURE__ */ O(C, { className: "h-3 w-3" })
243
+ })]
244
+ })
245
+ }) : null
246
+ ]
247
+ })]
248
+ })
192
249
  ]
193
- })
194
- });
250
+ }),
251
+ /* @__PURE__ */ k(te, { children: [/* @__PURE__ */ O(A, {
252
+ variant: "outline",
253
+ onClick: () => n(!1),
254
+ disabled: Z || Q,
255
+ children: "Cancel"
256
+ }), /* @__PURE__ */ O(A, {
257
+ onClick: () => {
258
+ if (!c.trim() || !u.trim()) {
259
+ i.warning("Missing details", "Title and slug are required.");
260
+ return;
261
+ }
262
+ if (!b.trim()) {
263
+ i.warning("Body required", "Write the article body before publishing.");
264
+ return;
265
+ }
266
+ let e = ee(S), n = {
267
+ title: c.trim(),
268
+ summary: v.trim() || null,
269
+ content: b.trim(),
270
+ tags: e,
271
+ status: E,
272
+ relatedAssetId: M || null,
273
+ relatedIncidentId: I || null
274
+ };
275
+ a && t ? ie({ variables: {
276
+ id: t.id,
277
+ input: n
278
+ } }) : Y({ variables: { input: {
279
+ ...n,
280
+ slug: u.trim()
281
+ } } });
282
+ },
283
+ disabled: Z || Q,
284
+ children: a ? Q ? "Saving…" : "Save changes" : Z ? "Publishing…" : "Publish article"
285
+ })] })
286
+ ] });
195
287
  }
196
- function ie({ article: e, onOpenChange: n }) {
197
- let r = t();
198
- return /* @__PURE__ */ C(A, {
288
+ function se({ article: e, onOpenChange: t, onEdit: r }) {
289
+ let i = n();
290
+ return /* @__PURE__ */ O(I, {
199
291
  open: !!e,
200
- onOpenChange: n,
201
- children: /* @__PURE__ */ C(j, {
292
+ onOpenChange: t,
293
+ children: /* @__PURE__ */ O(L, {
202
294
  className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl",
203
- children: e ? /* @__PURE__ */ w(S, { children: [
204
- /* @__PURE__ */ w(P, { children: [
205
- /* @__PURE__ */ w("div", {
206
- className: "flex flex-wrap items-center gap-2",
207
- children: [/* @__PURE__ */ C(J, { status: e.status }), e.tags.map((e) => /* @__PURE__ */ C("span", {
208
- className: "rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary",
209
- children: e
210
- }, e))]
211
- }),
212
- /* @__PURE__ */ C(F, {
213
- className: "mt-1",
214
- children: e.title
215
- }),
216
- /* @__PURE__ */ w(M, { children: [
217
- "Updated ",
218
- q(e.updatedAt),
219
- " · ",
220
- e.views.toLocaleString(),
221
- " views"
222
- ] })
223
- ] }),
224
- /* @__PURE__ */ C("div", {
295
+ children: e ? /* @__PURE__ */ k(D, { children: [
296
+ /* @__PURE__ */ k(z, { children: [/* @__PURE__ */ k("div", {
297
+ className: "flex flex-wrap items-center gap-2",
298
+ children: [/* @__PURE__ */ O($, { status: e.status }), e.tags.map((e) => /* @__PURE__ */ O("span", {
299
+ className: "rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary",
300
+ children: e
301
+ }, e))]
302
+ }), /* @__PURE__ */ k("div", {
303
+ className: "flex items-start justify-between gap-4",
304
+ children: [/* @__PURE__ */ k("div", {
305
+ className: "min-w-0 flex-1",
306
+ children: [/* @__PURE__ */ O(B, {
307
+ className: "mt-1",
308
+ children: e.title
309
+ }), /* @__PURE__ */ k(R, { children: [
310
+ "Updated ",
311
+ Z(e.updatedAt),
312
+ " · ",
313
+ e.views.toLocaleString(),
314
+ " views"
315
+ ] })]
316
+ }), /* @__PURE__ */ k(A, {
317
+ variant: "outline",
318
+ size: "sm",
319
+ onClick: () => {
320
+ t(!1), r(e);
321
+ },
322
+ children: [/* @__PURE__ */ O(y, { className: "mr-1.5 h-3.5 w-3.5" }), "Edit"]
323
+ })]
324
+ })] }),
325
+ /* @__PURE__ */ O("div", {
225
326
  className: "whitespace-pre-wrap text-sm leading-relaxed text-text-primary",
226
327
  children: e.content
227
328
  }),
228
- e.relatedAssetId || e.relatedIncidentId ? /* @__PURE__ */ w("div", {
329
+ e.relatedAssetId || e.relatedIncidentId ? /* @__PURE__ */ k("div", {
229
330
  className: "mt-4 space-y-2 border-t border-border-seam pt-4",
230
- children: [/* @__PURE__ */ w("h4", {
331
+ children: [/* @__PURE__ */ k("h4", {
231
332
  className: "flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-text-secondary",
232
- children: [/* @__PURE__ */ C(h, { className: "h-3.5 w-3.5" }), "Linked records"]
233
- }), /* @__PURE__ */ w("div", {
333
+ children: [/* @__PURE__ */ O(v, { className: "h-3.5 w-3.5" }), "Linked records"]
334
+ }), /* @__PURE__ */ k("div", {
234
335
  className: "flex flex-wrap gap-2",
235
- children: [e.relatedAssetId ? /* @__PURE__ */ w(T, {
336
+ children: [e.relatedAssetId ? /* @__PURE__ */ k(A, {
236
337
  variant: "outline",
237
338
  size: "sm",
238
339
  onClick: () => {
239
- n(!1), r(`/assets/${e.relatedAssetId}`);
340
+ t(!1), i(`/assets/${e.relatedAssetId}`);
240
341
  },
241
- children: ["View linked asset", /* @__PURE__ */ C(p, { className: "ml-1.5 h-3.5 w-3.5" })]
242
- }) : null, e.relatedIncidentId ? /* @__PURE__ */ w(T, {
342
+ children: ["View linked asset", /* @__PURE__ */ O(g, { className: "ml-1.5 h-3.5 w-3.5" })]
343
+ }) : null, e.relatedIncidentId ? /* @__PURE__ */ k(A, {
243
344
  variant: "outline",
244
345
  size: "sm",
245
346
  onClick: () => {
246
- n(!1), r(`/itil/incidents/${e.relatedIncidentId}`);
347
+ t(!1), i(`/itil/incidents/${e.relatedIncidentId}`);
247
348
  },
248
- children: ["View linked incident", /* @__PURE__ */ C(p, { className: "ml-1.5 h-3.5 w-3.5" })]
349
+ children: ["View linked incident", /* @__PURE__ */ O(g, { className: "ml-1.5 h-3.5 w-3.5" })]
249
350
  }) : null]
250
351
  })]
251
352
  }) : null
@@ -253,90 +354,96 @@ function ie({ article: e, onOpenChange: n }) {
253
354
  })
254
355
  });
255
356
  }
256
- function Y() {
257
- let [t, r] = d(!1), [p, h] = d(""), [y, b] = d("ALL"), [S, A] = d("ALL"), [j, M] = d("recent"), [N, P] = d(null), { data: F, loading: V, refetch: U } = x(H, { fetchPolicy: "cache-and-network" }), K = u(() => F?.knowledgeArticles ?? [], [F?.knowledgeArticles]), Y = u(() => {
357
+ function ce() {
358
+ let [e, n] = m(!1), [i, d] = m(null), [f, ee] = m(""), [g, v] = m("ALL"), [y, C] = m("ALL"), [w, T] = m("recent"), [D, I] = m(null), { data: L, loading: R, refetch: te } = E(ne, { fetchPolicy: "cache-and-network" }), z = p(() => L?.knowledgeArticles ?? [], [L?.knowledgeArticles]), B = p(() => {
258
359
  let e = /* @__PURE__ */ new Map();
259
- for (let t of K) for (let n of t.tags) e.set(n, (e.get(n) ?? 0) + 1);
360
+ for (let t of z) for (let n of t.tags) e.set(n, (e.get(n) ?? 0) + 1);
260
361
  return [...e.entries()].sort((e, t) => t[1] - e[1]);
261
- }, [K]), X = u(() => {
262
- let e = p.trim().toLowerCase();
263
- return [...K.filter((t) => y !== "ALL" && !t.tags.includes(y) || S !== "ALL" && t.status !== S ? !1 : e ? [
362
+ }, [z]), K = p(() => {
363
+ let e = f.trim().toLowerCase();
364
+ return [...z.filter((t) => g !== "ALL" && !t.tags.includes(g) || y !== "ALL" && t.status !== y ? !1 : e ? [
264
365
  t.title,
265
366
  t.summary ?? "",
266
367
  t.content,
267
368
  t.tags.join(" ")
268
- ].join(" ").toLowerCase().includes(e) : !0)].sort((e, t) => j === "views" ? t.views - e.views : new Date(t.updatedAt).getTime() - new Date(e.updatedAt).getTime());
369
+ ].join(" ").toLowerCase().includes(e) : !0)].sort((e, t) => w === "views" ? t.views - e.views : new Date(t.updatedAt).getTime() - new Date(e.updatedAt).getTime());
269
370
  }, [
270
- K,
271
- p,
371
+ z,
372
+ f,
373
+ g,
272
374
  y,
273
- S,
274
- j
275
- ]), Z = u(() => {
276
- let t = K.reduce((e, t) => (e[t.status] = (e[t.status] ?? 0) + 1, e), {});
277
- return Object.entries(t).map(([t, n], r) => ({
278
- name: G[t]?.label ?? t,
375
+ w
376
+ ]), q = p(() => {
377
+ let e = z.reduce((e, t) => (e[t.status] = (e[t.status] ?? 0) + 1, e), {});
378
+ return Object.entries(e).map(([e, n], r) => ({
379
+ name: Y[e]?.label ?? e,
279
380
  value: n,
280
- color: e(r)
381
+ color: t(r)
281
382
  }));
282
- }, [K]), Q = u(() => [...K].filter((e) => e.views > 0).sort((e, t) => t.views - e.views).slice(0, 6).map((e) => ({
383
+ }, [z]), J = p(() => [...z].filter((e) => e.views > 0).sort((e, t) => t.views - e.views).slice(0, 6).map((e) => ({
283
384
  name: e.title,
284
385
  views: e.views
285
- })), [K]), $ = s(X, {
386
+ })), [z]), X = c(K, {
286
387
  pageSize: 9,
287
- resetKey: `${p}|${y}|${S}|${j}`
388
+ resetKey: `${f}|${g}|${y}|${w}`
288
389
  });
289
- return /* @__PURE__ */ w("div", {
390
+ return /* @__PURE__ */ k("div", {
290
391
  className: "space-y-5 p-4 sm:p-6",
291
392
  children: [
292
- /* @__PURE__ */ C(l, {
393
+ /* @__PURE__ */ O(u, {
293
394
  hero: !0,
294
- icon: f,
395
+ icon: h,
295
396
  title: "Knowledge Base",
296
397
  description: "Turn one-off fixes and runbooks into repeatable, searchable operational knowledge.",
297
- actions: /* @__PURE__ */ w(T, {
398
+ actions: /* @__PURE__ */ k(A, {
298
399
  className: "w-full sm:w-auto",
299
- onClick: () => r(!0),
300
- children: [/* @__PURE__ */ C(g, { className: "mr-2 h-4 w-4" }), "New Article"]
400
+ onClick: () => {
401
+ d(null), n(!0);
402
+ },
403
+ children: [/* @__PURE__ */ O(b, { className: "mr-2 h-4 w-4" }), "New Article"]
301
404
  })
302
405
  }),
303
- /* @__PURE__ */ C(ee, { 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." }),
304
- /* @__PURE__ */ C(re, {
305
- open: t,
306
- onOpenChange: r,
307
- onPublished: () => void U()
406
+ /* @__PURE__ */ O(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." }),
407
+ /* @__PURE__ */ O(ae, {
408
+ open: e,
409
+ onOpenChange: n,
410
+ article: i,
411
+ onSaved: () => void te()
308
412
  }),
309
- /* @__PURE__ */ C(ie, {
310
- article: N,
311
- onOpenChange: (e) => !e && P(null)
413
+ /* @__PURE__ */ O(se, {
414
+ article: D,
415
+ onOpenChange: (e) => !e && I(null),
416
+ onEdit: (e) => {
417
+ I(null), d(e), n(!0);
418
+ }
312
419
  }),
313
- /* @__PURE__ */ w("div", {
420
+ /* @__PURE__ */ k("div", {
314
421
  className: "relative",
315
- children: [/* @__PURE__ */ C(_, { className: "absolute left-3.5 top-1/2 h-4.5 w-4.5 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ C(O, {
422
+ children: [/* @__PURE__ */ O(x, { className: "absolute left-3.5 top-1/2 h-4.5 w-4.5 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ O(N, {
316
423
  className: "h-12 pl-11 text-base",
317
424
  placeholder: "Search runbooks, fixes and procedures…",
318
- value: p,
319
- onChange: (e) => h(e.target.value),
425
+ value: f,
426
+ onChange: (e) => ee(e.target.value),
320
427
  "aria-label": "Search knowledge articles"
321
428
  })]
322
429
  }),
323
- K.length > 0 && (Z.length > 0 || Q.length > 0) ? /* @__PURE__ */ w("div", {
430
+ z.length > 0 && (q.length > 0 || J.length > 0) ? /* @__PURE__ */ k("div", {
324
431
  className: "grid gap-4 lg:grid-cols-2",
325
- children: [/* @__PURE__ */ C(c, {
432
+ children: [/* @__PURE__ */ O(l, {
326
433
  title: "Publishing status",
327
434
  subtitle: "Articles by status",
328
- children: /* @__PURE__ */ C(a, {
329
- data: Z,
435
+ children: /* @__PURE__ */ O(o, {
436
+ data: q,
330
437
  height: 200,
331
438
  centerLabel: "Articles",
332
- centerValue: K.length,
439
+ centerValue: z.length,
333
440
  ariaLabel: "Articles by status"
334
441
  })
335
- }), /* @__PURE__ */ C(c, {
442
+ }), /* @__PURE__ */ O(l, {
336
443
  title: "Most consulted",
337
444
  subtitle: "Top articles by views",
338
- children: /* @__PURE__ */ C(i, {
339
- data: Q,
445
+ children: /* @__PURE__ */ O(a, {
446
+ data: J,
340
447
  xKey: "name",
341
448
  series: [{
342
449
  key: "views",
@@ -350,89 +457,89 @@ function Y() {
350
457
  })
351
458
  })]
352
459
  }) : null,
353
- /* @__PURE__ */ w("div", {
460
+ /* @__PURE__ */ k("div", {
354
461
  className: "grid gap-5 lg:grid-cols-[200px_1fr]",
355
- children: [/* @__PURE__ */ w("aside", {
462
+ children: [/* @__PURE__ */ k("aside", {
356
463
  className: "space-y-3",
357
464
  children: [
358
- /* @__PURE__ */ w("div", {
465
+ /* @__PURE__ */ k("div", {
359
466
  className: "flex items-center justify-between gap-2",
360
- children: [/* @__PURE__ */ C("h3", {
467
+ children: [/* @__PURE__ */ O("h3", {
361
468
  className: "text-sm font-semibold text-text-primary",
362
469
  children: "Category"
363
- }), /* @__PURE__ */ w(I, {
364
- value: j,
365
- onValueChange: (e) => M(e),
366
- children: [/* @__PURE__ */ C(z, {
470
+ }), /* @__PURE__ */ k(V, {
471
+ value: w,
472
+ onValueChange: (e) => T(e),
473
+ children: [/* @__PURE__ */ O(W, {
367
474
  className: "h-8 w-[130px] text-xs lg:hidden",
368
- children: /* @__PURE__ */ C(B, {})
369
- }), /* @__PURE__ */ w(L, { children: [/* @__PURE__ */ C(R, {
475
+ children: /* @__PURE__ */ O(G, {})
476
+ }), /* @__PURE__ */ k(H, { children: [/* @__PURE__ */ O(U, {
370
477
  value: "recent",
371
478
  children: "Recently updated"
372
- }), /* @__PURE__ */ C(R, {
479
+ }), /* @__PURE__ */ O(U, {
373
480
  value: "views",
374
481
  children: "Most viewed"
375
482
  })] })]
376
483
  })]
377
484
  }),
378
- /* @__PURE__ */ w("div", {
485
+ /* @__PURE__ */ k("div", {
379
486
  className: "flex gap-2 overflow-x-auto pb-1 lg:flex-col lg:overflow-visible lg:pb-0",
380
- children: [/* @__PURE__ */ w("button", {
487
+ children: [/* @__PURE__ */ k("button", {
381
488
  type: "button",
382
- onClick: () => b("ALL"),
383
- className: n("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"),
384
- children: ["All articles", /* @__PURE__ */ C("span", {
489
+ onClick: () => v("ALL"),
490
+ className: r("shrink-0 rounded-md px-3 py-1.5 text-left text-sm font-medium transition-colors lg:w-full", g === "ALL" ? "bg-action-primary-bg/10 text-primary" : "text-text-secondary hover:text-text-primary"),
491
+ children: ["All articles", /* @__PURE__ */ O("span", {
385
492
  className: "ml-1.5 text-xs text-text-muted tabular-nums",
386
- children: K.length
493
+ children: z.length
387
494
  })]
388
- }), Y.map(([e, t]) => /* @__PURE__ */ w("button", {
495
+ }), B.map(([e, t]) => /* @__PURE__ */ k("button", {
389
496
  type: "button",
390
- onClick: () => b(e),
391
- className: 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", y === e ? "bg-action-primary-bg/10 text-primary" : "text-text-secondary hover:text-text-primary"),
392
- children: [/* @__PURE__ */ C("span", {
497
+ onClick: () => v(e),
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", g === e ? "bg-action-primary-bg/10 text-primary" : "text-text-secondary hover:text-text-primary"),
499
+ children: [/* @__PURE__ */ O("span", {
393
500
  className: "truncate",
394
501
  children: e
395
- }), /* @__PURE__ */ C("span", {
502
+ }), /* @__PURE__ */ O("span", {
396
503
  className: "text-xs text-text-muted tabular-nums",
397
504
  children: t
398
505
  })]
399
506
  }, e))]
400
507
  }),
401
- /* @__PURE__ */ w("div", {
508
+ /* @__PURE__ */ k("div", {
402
509
  className: "hidden space-y-1.5 border-t border-border-seam pt-3 lg:block",
403
510
  children: [
404
- /* @__PURE__ */ C(k, {
511
+ /* @__PURE__ */ O(P, {
405
512
  className: "text-xs",
406
513
  children: "Status"
407
514
  }),
408
- /* @__PURE__ */ w(I, {
409
- value: S,
410
- onValueChange: A,
411
- children: [/* @__PURE__ */ C(z, {
515
+ /* @__PURE__ */ k(V, {
516
+ value: y,
517
+ onValueChange: C,
518
+ children: [/* @__PURE__ */ O(W, {
412
519
  className: "h-9",
413
- children: /* @__PURE__ */ C(B, {})
414
- }), /* @__PURE__ */ w(L, { children: [/* @__PURE__ */ C(R, {
520
+ children: /* @__PURE__ */ O(G, {})
521
+ }), /* @__PURE__ */ k(H, { children: [/* @__PURE__ */ O(U, {
415
522
  value: "ALL",
416
523
  children: "All statuses"
417
- }), W.map((e) => /* @__PURE__ */ C(R, {
524
+ }), re.map((e) => /* @__PURE__ */ O(U, {
418
525
  value: e,
419
526
  children: e
420
527
  }, e))] })]
421
528
  }),
422
- /* @__PURE__ */ C(k, {
529
+ /* @__PURE__ */ O(P, {
423
530
  className: "text-xs",
424
531
  children: "Sort"
425
532
  }),
426
- /* @__PURE__ */ w(I, {
427
- value: j,
428
- onValueChange: (e) => M(e),
429
- children: [/* @__PURE__ */ C(z, {
533
+ /* @__PURE__ */ k(V, {
534
+ value: w,
535
+ onValueChange: (e) => T(e),
536
+ children: [/* @__PURE__ */ O(W, {
430
537
  className: "h-9",
431
- children: /* @__PURE__ */ C(B, {})
432
- }), /* @__PURE__ */ w(L, { children: [/* @__PURE__ */ C(R, {
538
+ children: /* @__PURE__ */ O(G, {})
539
+ }), /* @__PURE__ */ k(H, { children: [/* @__PURE__ */ O(U, {
433
540
  value: "recent",
434
541
  children: "Recently updated"
435
- }), /* @__PURE__ */ C(R, {
542
+ }), /* @__PURE__ */ O(U, {
436
543
  value: "views",
437
544
  children: "Most viewed"
438
545
  })] })]
@@ -440,73 +547,73 @@ function Y() {
440
547
  ]
441
548
  })
442
549
  ]
443
- }), /* @__PURE__ */ w("div", { children: [
444
- V && K.length === 0 ? /* @__PURE__ */ C("div", {
550
+ }), /* @__PURE__ */ k("div", { children: [
551
+ R && z.length === 0 ? /* @__PURE__ */ O("div", {
445
552
  className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3",
446
- children: Array.from({ length: 6 }).map((e, t) => /* @__PURE__ */ C("div", { className: "h-40 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer" }, t))
553
+ children: Array.from({ length: 6 }).map((e, t) => /* @__PURE__ */ O("div", { className: "h-40 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer" }, t))
447
554
  }) : null,
448
- !V && X.length === 0 ? /* @__PURE__ */ C(D, {
449
- illustration: p || y !== "ALL" || S !== "ALL" ? "empty-search" : "empty-data",
450
- title: p || y !== "ALL" || S !== "ALL" ? "No articles match your filters" : "No knowledge articles found",
451
- description: p || y !== "ALL" || S !== "ALL" ? "Try a different term, category or status." : "Publish an article to start building your knowledge base."
555
+ !R && K.length === 0 ? /* @__PURE__ */ O(M, {
556
+ illustration: f || g !== "ALL" || y !== "ALL" ? "empty-search" : "empty-data",
557
+ title: f || g !== "ALL" || y !== "ALL" ? "No articles match your filters" : "No knowledge articles found",
558
+ description: f || g !== "ALL" || y !== "ALL" ? "Try a different term, category or status." : "Publish an article to start building your knowledge base."
452
559
  }) : null,
453
- /* @__PURE__ */ C("div", {
560
+ /* @__PURE__ */ O("div", {
454
561
  className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3",
455
- children: $.pageItems.map((e) => /* @__PURE__ */ C(E, {
562
+ children: X.pageItems.map((e) => /* @__PURE__ */ O(j, {
456
563
  treatment: "glass",
457
564
  className: "border-border-seam",
458
- children: /* @__PURE__ */ w("button", {
565
+ children: /* @__PURE__ */ k("button", {
459
566
  type: "button",
460
- onClick: () => P(e),
567
+ onClick: () => I(e),
461
568
  className: "flex h-full w-full flex-col gap-3 p-4 text-left",
462
569
  children: [
463
- /* @__PURE__ */ w("div", {
570
+ /* @__PURE__ */ k("div", {
464
571
  className: "flex flex-wrap items-center gap-1.5",
465
- children: [/* @__PURE__ */ C(J, { status: e.status }), e.tags.slice(0, 1).map((e) => /* @__PURE__ */ C("span", {
572
+ children: [/* @__PURE__ */ O($, { status: e.status }), e.tags.slice(0, 1).map((e) => /* @__PURE__ */ O("span", {
466
573
  className: "rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary",
467
574
  children: e
468
575
  }, e))]
469
576
  }),
470
- /* @__PURE__ */ C("h2", {
577
+ /* @__PURE__ */ O("h2", {
471
578
  className: "line-clamp-2 text-base font-semibold text-text-primary",
472
579
  children: e.title
473
580
  }),
474
- /* @__PURE__ */ C("p", {
581
+ /* @__PURE__ */ O("p", {
475
582
  className: "line-clamp-2 flex-1 text-sm text-text-secondary",
476
- children: te(e)
583
+ children: ie(e)
477
584
  }),
478
- /* @__PURE__ */ w("div", {
585
+ /* @__PURE__ */ k("div", {
479
586
  className: "flex items-center justify-between gap-2 border-t border-border-seam/60 pt-3",
480
- children: [/* @__PURE__ */ w("div", {
587
+ children: [/* @__PURE__ */ k("div", {
481
588
  className: "flex min-w-0 items-center gap-2",
482
- children: [/* @__PURE__ */ C("span", {
589
+ children: [/* @__PURE__ */ O("span", {
483
590
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-[10px] font-semibold text-text-secondary",
484
- children: e.updatedBy ? ne(e.updatedBy) : /* @__PURE__ */ C(v, { className: "h-3 w-3" })
485
- }), /* @__PURE__ */ w("span", {
591
+ children: e.updatedBy ? Q(e.updatedBy) : /* @__PURE__ */ O(S, { className: "h-3 w-3" })
592
+ }), /* @__PURE__ */ k("span", {
486
593
  className: "truncate text-xs text-text-secondary",
487
- children: ["Updated ", q(e.updatedAt)]
594
+ children: ["Updated ", Z(e.updatedAt)]
488
595
  })]
489
- }), /* @__PURE__ */ w("span", {
596
+ }), /* @__PURE__ */ k("span", {
490
597
  className: "flex shrink-0 items-center gap-1 text-xs text-text-muted tabular-nums",
491
- children: [/* @__PURE__ */ C(m, { className: "h-3.5 w-3.5" }), e.views.toLocaleString()]
598
+ children: [/* @__PURE__ */ O(_, { className: "h-3.5 w-3.5" }), e.views.toLocaleString()]
492
599
  })]
493
600
  })
494
601
  ]
495
602
  })
496
603
  }, e.id))
497
604
  }),
498
- /* @__PURE__ */ C("div", {
605
+ /* @__PURE__ */ O("div", {
499
606
  className: "mt-4",
500
- children: /* @__PURE__ */ C(o, {
501
- page: $.page,
502
- pageCount: $.pageCount,
503
- rangeStart: $.rangeStart,
504
- rangeEnd: $.rangeEnd,
505
- totalItems: $.totalItems,
506
- canPrev: $.canPrev,
507
- canNext: $.canNext,
508
- onPrev: $.prevPage,
509
- onNext: $.nextPage,
607
+ children: /* @__PURE__ */ O(s, {
608
+ page: X.page,
609
+ pageCount: X.pageCount,
610
+ rangeStart: X.rangeStart,
611
+ rangeEnd: X.rangeEnd,
612
+ totalItems: X.totalItems,
613
+ canPrev: X.canPrev,
614
+ canNext: X.canNext,
615
+ onPrev: X.prevPage,
616
+ onNext: X.nextPage,
510
617
  itemLabel: "articles"
511
618
  })
512
619
  })
@@ -516,6 +623,6 @@ function Y() {
516
623
  });
517
624
  }
518
625
  //#endregion
519
- export { Y as KnowledgeBasePage };
626
+ export { ce as KnowledgeBasePage };
520
627
 
521
628
  //# 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 } 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 { cn } from '../utils/cn';\nimport { notify } from '../utils/toast';\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\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\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 PublishArticleDialog({\n open,\n onOpenChange,\n onPublished,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onPublished: () => void;\n}) {\n const [title, setTitle] = useState('');\n const [slug, setSlug] = useState('');\n const [slugTouched, setSlugTouched] = useState(false);\n const [summary, setSummary] = useState('');\n const [content, setContent] = useState('');\n const [tags, setTags] = useState('');\n const [status, setStatus] = useState<(typeof ARTICLE_STATUSES)[number]>('PUBLISHED');\n\n const reset = () => {\n setTitle('');\n setSlug('');\n setSlugTouched(false);\n setSummary('');\n setContent('');\n setTags('');\n setStatus('PUBLISHED');\n };\n\n const [createArticle, { loading }] = 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 onPublished();\n },\n onError: (error) => notify.error('Could not publish article', error.message),\n }\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 const parsedTags = tags\n .split(',')\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n void createArticle({\n variables: {\n input: {\n title: title.trim(),\n slug: slug.trim(),\n summary: summary.trim() || null,\n content: content.trim(),\n tags: parsedTags,\n status,\n },\n },\n });\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-2xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New Article</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\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 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={(v) => setStatus(v as typeof status)}>\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 </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={loading}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={loading}>\n {loading ? 'Publishing…' : 'Publish article'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nfunction ArticleReader({\n article,\n onOpenChange,\n}: {\n article: KnowledgeArticle | null;\n onOpenChange: (open: boolean) => 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 <ResponsiveDialogTitle className=\"mt-1\">{article.title}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Updated {formatDate(article.updatedAt)} · {article.views.toLocaleString()} views\n </ResponsiveDialogDescription>\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 [dialogOpen, setDialogOpen] = useState(false);\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 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={() => setDialogOpen(true)}>\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&apos;t start\n from scratch. Link articles to incidents and assets, then search them when troubleshooting.\n </PagePurpose>\n\n <PublishArticleDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onPublished={() => void refetch()}\n />\n <ArticleReader article={reading} onOpenChange={(open) => !open && setReading(null)} />\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":";;;;;;;;;;;;;;;;;AAsCA,IAAM,IAA2B,CAAG;;;;;;;;;;;;;;;;;;;;GAsB9B,IAAoC,CAAG;;;;;;;GA+BvC,IAAmB;CAAC;CAAS;CAAa;CAAW,EAErD,IAAoE;CACxE,WAAW;EACT,OAAO;EACP,WAAW;EACZ;CACD,OAAO;EAAE,OAAO;EAAS,WAAW;EAAoC;CACxE,UAAU;EACR,OAAO;EACP,WAAW;EACZ;CACF,EAEK,KAAW,MACf,EACG,aAAa,CACb,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,EAEtB,KAAc,MAClB,IACI,IAAI,KAAK,EAAM,CAAC,mBAAmB,KAAA,GAAW;CAC5C,MAAM;CACN,OAAO;CACP,KAAK;CACN,CAAC,GACF;AAGN,SAAS,GAAU,GAAmC;AACpD,KAAI,EAAQ,WAAW,EAAQ,QAAQ,MAAM,CAAE,QAAO,EAAQ,QAAQ,MAAM;CAC5E,IAAM,IAAO,EAAQ,QAAQ,QAAQ,QAAQ,IAAI,CAAC,MAAM;AACxD,QAAO,EAAK,SAAS,MAAM,GAAG,EAAK,MAAM,GAAG,IAAI,CAAC,KAAK;;AAIxD,SAAS,GAAW,GAAuB;AAEzC,SADc,EAAM,QAAQ,iBAAiB,GAAG,CAClC,MAAM,GAAG,EAAE,IAAI,KAAK,aAAa;;AAGjD,SAAS,EAAW,EAAE,aAA8B;CAClD,IAAM,IAAO,EAAY,MAAW;EAClC,OAAO;EACP,WAAW;EACZ;AACD,QACE,kBAAC,QAAD;EACE,WAAW,EACT,yEACA,EAAK,UACN;YAEA,EAAK;EACD,CAAA;;AAIX,SAAS,GAAqB,EAC5B,SACA,iBACA,kBAKC;CACD,IAAM,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAQ,KAAa,EAA4C,YAAY,EAE9E,UAAc;AAOlB,EANA,EAAS,GAAG,EACZ,EAAQ,GAAG,EACX,EAAe,GAAM,EACrB,EAAW,GAAG,EACd,EAAW,GAAG,EACd,EAAQ,GAAG,EACX,EAAU,YAAY;IAGlB,CAAC,GAAe,EAAE,gBAAa,EACnC,GACA;EACE,cAAc,MAAS;AAOrB,GANA,EAAO,QACL,qBACA,GAAG,EAAK,uBAAuB,MAAM,oBACtC,EACD,GAAO,EACP,EAAa,GAAM,EACnB,GAAa;;EAEf,UAAU,MAAU,EAAO,MAAM,6BAA6B,EAAM,QAAQ;EAC7E,CACF,EAEK,KAAqB,MAAiB;AAE1C,EADA,EAAS,EAAK,EACT,KAAa,EAAQ,EAAQ,EAAK,CAAC;;AA8B1C,QACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,eAAmC,CAAA,EAC1D,kBAAC,GAAD,EAAA,UAA6B,4DAEC,CAAA,CACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,WAAe,CAAA,EACtB,kBAAC,GAAD;SAAO,OAAO;SAAO,WAAW,MAAM,EAAkB,EAAE,OAAO,MAAM;SAAI,CAAA,CACvE;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,UAAc,CAAA,EACrB,kBAAC,GAAD;SACE,OAAO;SACP,WAAW,MAAM;AAEf,UADA,EAAe,GAAK,EACpB,EAAQ,EAAE,OAAO,MAAM;;SAEzB,CAAA,CACE;UACF;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,WAAe,CAAA,EACtB,kBAAC,GAAD;QACE,aAAY;QACZ,OAAO;QACP,WAAW,MAAM,EAAW,EAAE,OAAO,MAAM;QAC3C,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,UAAc,CAAA,EACrB,kBAAC,GAAD;QACE,aAAY;QACZ,OAAO;QACP,WAAW,MAAM,EAAW,EAAE,OAAO,MAAM;QAC3C,MAAM;QACN,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,iBAAqB,CAAA,EAC5B,kBAAC,GAAD;SACE,aAAY;SACZ,OAAO;SACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;SACxC,CAAA,CACE;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,UAAc,CAAA,EACrB,kBAAC,GAAD;SAAQ,OAAO;SAAQ,gBAAgB,MAAM,EAAU,EAAmB;mBAA1E,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAe,CAAA,EACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAiB,KAAK,MACrB,kBAAC,GAAD;UAAoB,OAAO;oBACxB;UACU,EAFI,EAEJ,CACb,EACY,CAAA,CACT;WACL;UACF;;MACF;;IACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAa,GAAM;KAAE,UAAU;eAAS;KAExE,CAAA,EACT,kBAAC,GAAD;KAAQ,eApGW;AACzB,UAAI,CAAC,EAAM,MAAM,IAAI,CAAC,EAAK,MAAM,EAAE;AACjC,SAAO,QAAQ,mBAAmB,+BAA+B;AACjE;;AAEF,UAAI,CAAC,EAAQ,MAAM,EAAE;AACnB,SAAO,QAAQ,iBAAiB,4CAA4C;AAC5E;;MAEF,IAAM,IAAa,EAChB,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE;AACzB,QAAc,EACjB,WAAW,EACT,OAAO;OACL,OAAO,EAAM,MAAM;OACnB,MAAM,EAAK,MAAM;OACjB,SAAS,EAAQ,MAAM,IAAI;OAC3B,SAAS,EAAQ,MAAM;OACvB,MAAM;OACN;OACD,EACF,EACF,CAAC;;KA4EmC,UAAU;eACtC,IAAU,gBAAgB;KACpB,CAAA,CACc,EAAA,CAAA;IACD;;EACT,CAAA;;AAIvB,SAAS,GAAc,EACrB,YACA,mBAIC;CACD,IAAM,IAAW,GAAyB;AAC1C,QACE,kBAAC,GAAD;EAAkB,MAAM,EAAQ;EAAwB;YACtD,kBAAC,GAAD;GAAyB,WAAU;aAChC,IACC,kBAAA,GAAA,EAAA,UAAA;IACE,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAY,QAAQ,EAAQ,QAAU,CAAA,EACrC,EAAQ,KAAK,KAAK,MACjB,kBAAC,QAAD;OAEE,WAAU;iBAET;OACI,EAJA,EAIA,CACP,CACE;;KACN,kBAAC,GAAD;MAAuB,WAAU;gBAAQ,EAAQ;MAA8B,CAAA;KAC/E,kBAAC,GAAD,EAAA,UAAA;MAA6B;MAClB,EAAW,EAAQ,UAAU;MAAC;MAAI,EAAQ,MAAM,gBAAgB;MAAC;MAC9C,EAAA,CAAA;KACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAQ;KACL,CAAA;IACL,EAAQ,kBAAkB,EAAQ,oBACjC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAd,CACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA,EAAA,iBAE9B;SACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,EAAQ,iBACP,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,eAAe;AAEb,QADA,EAAa,GAAM,EACnB,EAAS,WAAW,EAAQ,iBAAiB;;iBALjD,CAOC,qBAEC,kBAAC,GAAD,EAAc,WAAU,sBAAuB,CAAA,CACxC;WACP,MACH,EAAQ,oBACP,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,eAAe;AAEb,QADA,EAAa,GAAM,EACnB,EAAS,mBAAmB,EAAQ,oBAAoB;;iBAL5D,CAOC,wBAEC,kBAAC,GAAD,EAAc,WAAU,sBAAuB,CAAA,CACxC;WACP,KACA;QACF;SACJ;IACH,EAAA,CAAA,GACD;GACoB,CAAA;EACT,CAAA;;AAMvB,SAAgB,IAAoB;CAClC,IAAM,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAU,KAAe,EAAiB,MAAM,EACjD,CAAC,GAAc,KAAmB,EAAiB,MAAM,EACzD,CAAC,GAAM,KAAW,EAAkB,SAAS,EAC7C,CAAC,GAAS,KAAc,EAAkC,KAAK,EAE/D,EAAE,SAAM,YAAS,eAAY,EACjC,GACA,EAAE,aAAa,qBAAqB,CACrC,EAEK,IAAW,QAAc,GAAM,qBAAqB,EAAE,EAAE,CAAC,GAAM,kBAAkB,CAAC,EAElF,IAAa,QAAc;EAC/B,IAAM,oBAAS,IAAI,KAAqB;AACxC,OAAK,IAAM,KAAK,EACd,MAAK,IAAM,KAAO,EAAE,KAAM,GAAO,IAAI,IAAM,EAAO,IAAI,EAAI,IAAI,KAAK,EAAE;AAEvE,SAAO,CAAC,GAAG,EAAO,SAAS,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG;IACvD,CAAC,EAAS,CAAC,EAER,IAAW,QAAc;EAC7B,IAAM,IAAO,EAAO,MAAM,CAAC,aAAa;AAexC,SAAO,CAAC,GAdK,EAAS,QAAQ,MACxB,MAAa,SAAS,CAAC,EAAQ,KAAK,SAAS,EAAS,IACtD,MAAiB,SAAS,EAAQ,WAAW,IAAqB,KACjE,IACY;GACf,EAAQ;GACR,EAAQ,WAAW;GACnB,EAAQ;GACR,EAAQ,KAAK,KAAK,IAAI;GACvB,CACE,KAAK,IAAI,CACT,aAAa,CACA,SAAS,EAAK,GATZ,GAUlB,CACc,CAAC,MAAM,GAAG,MACxB,MAAS,UACL,EAAE,QAAQ,EAAE,QACZ,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CACtE;IACA;EAAC;EAAU;EAAQ;EAAU;EAAc;EAAK,CAAC,EAE9C,IAAY,QAA4B;EAC5C,IAAM,IAAS,EAAS,QAAgC,GAAK,OAC3D,EAAI,EAAE,WAAW,EAAI,EAAE,WAAW,KAAK,GAChC,IACN,EAAE,CAAC;AACN,SAAO,OAAO,QAAQ,EAAO,CAAC,KAAK,CAAC,GAAQ,IAAQ,OAAO;GACzD,MAAM,EAAY,IAAS,SAAS;GACpC;GACA,OAAO,EAAY,EAAE;GACtB,EAAE;IACF,CAAC,EAAS,CAAC,EAER,IAAa,QAEf,CAAC,GAAG,EAAS,CACV,QAAQ,MAAM,EAAE,QAAQ,EAAE,CAC1B,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CACjC,MAAM,GAAG,EAAE,CACX,KAAK,OAAO;EAAE,MAAM,EAAE;EAAO,OAAO,EAAE;EAAO,EAAE,EACpD,CAAC,EAAS,CACX,EAEK,IAAa,EAAc,GAAU;EACzC,UAAU;EACV,UAAU,GAAG,EAAO,GAAG,EAAS,GAAG,EAAa,GAAG;EACpD,CAAC;AAEF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,MAAA;IACA,MAAM;IACN,OAAM;IACN,aAAY;IACZ,SACE,kBAAC,GAAD;KAAQ,WAAU;KAAmB,eAAe,EAAc,GAAK;eAAvE,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,cAE1B;;IAEX,CAAA;GACF,kBAAC,IAAD,EAAA,UAAa,mLAGC,CAAA;GAEd,kBAAC,IAAD;IACE,MAAM;IACN,cAAc;IACd,mBAAmB,KAAK,GAAS;IACjC,CAAA;GACF,kBAAC,IAAD;IAAe,SAAS;IAAS,eAAe,MAAS,CAAC,KAAQ,EAAW,KAAK;IAAI,CAAA;GAEtF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,8EAA+E,CAAA,EACjG,kBAAC,GAAD;KACE,WAAU;KACV,aAAY;KACZ,OAAO;KACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;KAC1C,cAAW;KACX,CAAA,CACE;;GAEL,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;MACV,CAAA;KACQ,CAAA,EACZ,kBAAC,GAAD;KAAW,OAAM;KAAiB,UAAS;eACzC,kBAAC,GAAD;MACE,MAAM;MACN,MAAK;MACL,QAAQ,CAAC;OAAE,KAAK;OAAS,OAAO;OAAS,CAAC;MAC1C,aAAY;MACZ,QAAQ;MACR,eAAe;MACf,YAAW;MACX,WAAU;MACV,CAAA;KACQ,CAAA,CACR;QACJ;GAEJ,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,SAAD;KAAO,WAAU;eAAjB;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAa,CAAA,EACrE,kBAAC,GAAD;QAAQ,OAAO;QAAM,gBAAgB,MAAM,EAAQ,EAAa;kBAAhE,CACE,kBAAC,GAAD;SAAe,WAAU;mBACvB,kBAAC,GAAD,EAAe,CAAA;SACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;SAAY,OAAM;mBAAS;SAA6B,CAAA,EACxD,kBAAC,GAAD;SAAY,OAAM;mBAAQ;SAAwB,CAAA,CACpC,EAAA,CAAA,CACT;UACL;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAY,MAAM;QACjC,WAAW,EACT,6FACA,MAAa,QACT,yCACA,8CACL;kBARH,CASC,gBAEC,kBAAC,QAAD;SAAM,WAAU;mBAA+C,EAAS;SAAc,CAAA,CAC/E;WACR,EAAW,KAAK,CAAC,GAAK,OACrB,kBAAC,UAAD;QAEE,MAAK;QACL,eAAe,EAAY,EAAI;QAC/B,WAAW,EACT,qIACA,MAAa,IACT,yCACA,8CACL;kBATH,CAWE,kBAAC,QAAD;SAAM,WAAU;mBAAY;SAAW,CAAA,EACvC,kBAAC,QAAD;SAAM,WAAU;mBAAwC;SAAa,CAAA,CAC9D;UAZF,EAYE,CACT,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAO,WAAU;mBAAU;SAAc,CAAA;QACzC,kBAAC,GAAD;SAAQ,OAAO;SAAc,eAAe;mBAA5C,CACE,kBAAC,GAAD;UAAe,WAAU;oBACvB,kBAAC,GAAD,EAAe,CAAA;UACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;UAAY,OAAM;oBAAM;UAAyB,CAAA,EAChD,EAAiB,KAAK,MACrB,kBAAC,GAAD;UAAoB,OAAO;oBACxB;UACU,EAFI,EAEJ,CACb,CACY,EAAA,CAAA,CACT;;QACT,kBAAC,GAAD;SAAO,WAAU;mBAAU;SAAY,CAAA;QACvC,kBAAC,GAAD;SAAQ,OAAO;SAAM,gBAAgB,MAAM,EAAQ,EAAa;mBAAhE,CACE,kBAAC,GAAD;UAAe,WAAU;oBACvB,kBAAC,GAAD,EAAe,CAAA;UACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;UAAY,OAAM;oBAAS;UAA6B,CAAA,EACxD,kBAAC,GAAD;UAAY,OAAM;oBAAQ;UAAwB,CAAA,CACpC,EAAA,CAAA,CACT;;QACL;;MACA;QAGR,kBAAC,OAAD,EAAA,UAAA;KACG,KAAW,EAAS,WAAW,IAC9B,kBAAC,OAAD;MAAK,WAAU;gBACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,OAAD,EAEE,WAAU,+DACV,EAFK,EAEL,CACF;MACE,CAAA,GACJ;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;MAEN,CAAA,GACA;KACJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAW,UAAU,KAAK,MACzB,kBAAC,GAAD;OAA4B,WAAU;OAAQ,WAAU;iBACtD,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAW,EAAQ;QAClC,WAAU;kBAHZ;SAKE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAY,QAAQ,EAAQ,QAAU,CAAA,EACrC,EAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC7B,kBAAC,QAAD;WAEE,WAAU;qBAET;WACI,EAJA,EAIA,CACP,CACE;;SACN,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAQ;UACN,CAAA;SACL,kBAAC,KAAD;UAAG,WAAU;oBACV,GAAU,EAAQ;UACjB,CAAA;SACJ,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAQ,YACP,GAAW,EAAQ,UAAU,GAE7B,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;YAEzB,CAAA,EACP,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAAuD,YAC5C,EAAW,EAAQ,UAAU,CACjC;cACH;cACN,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD,EAAK,WAAU,eAAgB,CAAA,EAC9B,EAAQ,MAAM,gBAAgB,CAC1B;aACH;;SACC;;OACC,EA1CI,EAAQ,GA0CZ,CACZ;MACE,CAAA;KACN,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;OACV,CAAA;MACE,CAAA;KACF,EAAA,CAAA,CACF;;GACF"}
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&apos;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":"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"}
@@ -0,0 +1,17 @@
1
+ import { Asset, Incident } from '../store/assethandlerStore';
2
+
3
+ /**
4
+ * Parse a comma-separated tag string into a clean list of non-empty tags.
5
+ */
6
+ export declare function parseTagsInput(value: string): string[];
7
+ /**
8
+ * Format an asset as a picker option label.
9
+ * Falls back to the asset tag or id so the option is always identifiable.
10
+ */
11
+ export declare function formatAssetOption(asset: Asset): string;
12
+ /**
13
+ * Format an incident as a picker option label.
14
+ * Falls back to the incident id so the option is always identifiable.
15
+ */
16
+ export declare function formatIncidentOption(incident: Incident): string;
17
+ //# sourceMappingURL=knowledgeBase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledgeBase.d.ts","sourceRoot":"","sources":["../../../src/assethandler/utils/knowledgeBase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAElE;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAKtD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAKtD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAI/D"}
@@ -0,0 +1,17 @@
1
+ //#region src/assethandler/utils/knowledgeBase.ts
2
+ function e(e) {
3
+ return e.split(",").map((e) => e.trim()).filter((e) => e.length > 0);
4
+ }
5
+ function t(e) {
6
+ let t = e.name?.trim();
7
+ if (t) return t;
8
+ let n = e.assetTag?.trim();
9
+ return n ? `Tag: ${n}` : e.id;
10
+ }
11
+ function n(e) {
12
+ return e.title?.trim() || e.id;
13
+ }
14
+ //#endregion
15
+ export { t as formatAssetOption, n as formatIncidentOption, e as parseTagsInput };
16
+
17
+ //# sourceMappingURL=knowledgeBase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledgeBase.js","names":[],"sources":["../../../src/assethandler/utils/knowledgeBase.ts"],"sourcesContent":["import type { Asset, Incident } from '../store/assethandlerStore';\n\n/**\n * Parse a comma-separated tag string into a clean list of non-empty tags.\n */\nexport function parseTagsInput(value: string): string[] {\n return value\n .split(',')\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n}\n\n/**\n * Format an asset as a picker option label.\n * Falls back to the asset tag or id so the option is always identifiable.\n */\nexport function formatAssetOption(asset: Asset): string {\n const primary = asset.name?.trim();\n if (primary) return primary;\n const fallback = asset.assetTag?.trim();\n return fallback ? `Tag: ${fallback}` : asset.id;\n}\n\n/**\n * Format an incident as a picker option label.\n * Falls back to the incident id so the option is always identifiable.\n */\nexport function formatIncidentOption(incident: Incident): string {\n const primary = incident.title?.trim();\n if (primary) return primary;\n return incident.id;\n}\n"],"mappings":";AAKA,SAAgB,EAAe,GAAyB;AACtD,QAAO,EACJ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE;;AAOhC,SAAgB,EAAkB,GAAsB;CACtD,IAAM,IAAU,EAAM,MAAM,MAAM;AAClC,KAAI,EAAS,QAAO;CACpB,IAAM,IAAW,EAAM,UAAU,MAAM;AACvC,QAAO,IAAW,QAAQ,MAAa,EAAM;;AAO/C,SAAgB,EAAqB,GAA4B;AAG/D,QAFgB,EAAS,OAAO,MAAM,IAE/B,EAAS"}
@@ -0,0 +1,2 @@
1
+
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/test/setup.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-assethandler",
3
- "version": "2026.820.1",
3
+ "version": "2026.820.3",
4
4
  "description": "Burdenoff AssetHandler Microfrontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -33,7 +33,9 @@
33
33
  "lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
34
34
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
35
35
  "type:check": "bash scripts/build-limits.sh tsc --noEmit",
36
- "test": "bun test",
36
+ "test": "bash scripts/build-limits.sh vitest run",
37
+ "test:watch": "vitest",
38
+ "test:coverage": "bash scripts/build-limits.sh vitest run --coverage",
37
39
  "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build'",
38
40
  "codegen:all": "bun run schema:pull && bun run codegen",
39
41
  "switch:status": "bun scripts/deps-mode.ts status",
@@ -81,7 +83,12 @@
81
83
  "@storybook/test-runner": "0.24.4",
82
84
  "@tailwindcss/vite": "^4.3.2",
83
85
  "@types/bun": "^1.3.9",
86
+ "@testing-library/jest-dom": "^6.6.3",
87
+ "@testing-library/react": "^16.1.0",
88
+ "@vitest/coverage-v8": "^3.2.4",
84
89
  "@vitejs/plugin-react": "^4.3.4",
90
+ "happy-dom": "^17.0.0",
91
+ "vitest": "^3.2.4",
85
92
  "autoprefixer": "^10.4.20",
86
93
  "eslint": "^9.18.0",
87
94
  "eslint-plugin-react": "^7.37.5",