@burdenoff/microfe-assethandler 2026.719.2 → 2026.731.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EsgDashboardPage.js","names":[],"sources":["../../../src/assethandler/pages/EsgDashboardPage.tsx"],"sourcesContent":["import { Leaf, Plus, TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport type React from 'react';\nimport {\n Button,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\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 StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n seriesColor,\n formatNumber,\n type DonutSlice,\n} from '../components';\nimport { cn } from '../utils/cn';\nimport { notify } from '../utils/toast';\n\nconst ESG_METRICS_QUERY = gql`\n query AssetHandlerEsgMetrics {\n esgMetrics {\n id\n metricKey\n metricName\n category\n unit\n value\n target\n trend\n periodStart\n periodEnd\n }\n }\n`;\n\nconst UPSERT_ESG_METRIC_MUTATION = gql`\n mutation AssetHandlerUpsertEsgMetric($input: UpsertEsgMetricInput!) {\n upsertEsgMetric(input: $input) {\n id\n metricName\n }\n }\n`;\n\ninterface EsgMetric {\n id: string;\n metricKey: string;\n metricName: string;\n category: string;\n unit: string;\n value: number;\n target: number | null;\n trend: string | null;\n periodStart: string;\n periodEnd: string;\n}\n\ninterface UpsertEsgMetricResult {\n upsertEsgMetric: { id: string; metricName: string };\n}\n\nconst ESG_CATEGORIES = ['environment', 'social', 'governance'] as const;\nconst ESG_TRENDS = ['improving', 'stable', 'declining'] as const;\n\nconst TREND_META: Record<\n string,\n {\n label: string;\n icon: typeof TrendingUp;\n tone: 'positive' | 'negative' | 'neutral';\n dir: 'up' | 'down' | 'neutral';\n }\n> = {\n improving: { label: 'Improving', icon: TrendingUp, tone: 'positive', dir: 'up' },\n declining: { label: 'Declining', icon: TrendingDown, tone: 'negative', dir: 'down' },\n stable: { label: 'Stable', icon: Minus, tone: 'neutral', dir: 'neutral' },\n};\n\nconst TREND_PILL: Record<'positive' | 'negative' | 'neutral', string> = {\n positive: 'bg-status-success-bg-subtle text-status-success-text',\n negative: 'bg-status-error-bg-subtle text-status-error-text',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\nfunction firstOfMonthISO(): string {\n const now = new Date();\n return new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10);\n}\n\nfunction todayISO(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction TrendBadge({ trend }: { trend: string | null }) {\n const meta = TREND_META[trend ?? 'stable'] ?? TREND_META.stable;\n const Icon = meta.icon;\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',\n TREND_PILL[meta.tone]\n )}\n >\n <Icon className=\"h-3 w-3\" />\n {meta.label}\n </span>\n );\n}\n\nfunction RecordMetricDialog({\n open,\n onOpenChange,\n onSaved,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSaved: () => void;\n}) {\n const [metricName, setMetricName] = useState('');\n const [category, setCategory] = useState<(typeof ESG_CATEGORIES)[number]>('environment');\n const [unit, setUnit] = useState('');\n const [value, setValue] = useState('');\n const [target, setTarget] = useState('');\n const [trend, setTrend] = useState<(typeof ESG_TRENDS)[number]>('stable');\n const [periodStart, setPeriodStart] = useState(firstOfMonthISO());\n const [periodEnd, setPeriodEnd] = useState(todayISO());\n\n const [upsertEsgMetric, { loading: saving }] = useMutation<UpsertEsgMetricResult>(\n UPSERT_ESG_METRIC_MUTATION,\n {\n onCompleted: (result) => {\n notify.success('Metric saved', `${result.upsertEsgMetric.metricName} was recorded.`);\n setMetricName('');\n setUnit('');\n setValue('');\n setTarget('');\n onOpenChange(false);\n onSaved();\n },\n onError: (error) => notify.error('Could not save metric', error.message),\n }\n );\n\n const handleSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n const parsedValue = Number(value);\n if (!metricName.trim() || !unit.trim()) {\n notify.warning('Missing details', 'Metric name and unit are required.');\n return;\n }\n if (!Number.isFinite(parsedValue) || value.trim() === '') {\n notify.warning('Invalid value', 'Enter a numeric metric value.');\n return;\n }\n if (!periodStart || !periodEnd) {\n notify.warning('Missing period', 'Period start and end are required.');\n return;\n }\n const parsedTarget = target.trim() === '' ? null : Number(target);\n void upsertEsgMetric({\n variables: {\n input: {\n metricKey: slugify(metricName),\n metricName: metricName.trim(),\n category,\n unit: unit.trim(),\n value: parsedValue,\n target: parsedTarget != null && Number.isFinite(parsedTarget) ? parsedTarget : null,\n trend,\n periodStart: new Date(periodStart).toISOString(),\n periodEnd: new Date(periodEnd).toISOString(),\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>Save Metric</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record an ESG metric to track progress toward your sustainability targets.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form className=\"space-y-3\" onSubmit={handleSubmit}>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <div className=\"space-y-2\">\n <Label>Metric Name *</Label>\n <Input\n placeholder=\"e.g., Scope 2 carbon emissions\"\n value={metricName}\n onChange={(e) => setMetricName(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Category</Label>\n <Select value={category} onValueChange={(v) => setCategory(v as typeof category)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ESG_CATEGORIES.map((c) => (\n <SelectItem key={c} value={c} className=\"capitalize\">\n {c}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-2\">\n <Label>Unit *</Label>\n <Input\n placeholder=\"e.g., tCO2e\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n />\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <div className=\"space-y-2\">\n <Label>Value *</Label>\n <Input\n type=\"number\"\n step=\"any\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Target</Label>\n <Input\n type=\"number\"\n step=\"any\"\n value={target}\n onChange={(e) => setTarget(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Trend</Label>\n <Select value={trend} onValueChange={(v) => setTrend(v as typeof trend)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ESG_TRENDS.map((t) => (\n <SelectItem key={t} value={t} className=\"capitalize\">\n {t}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Period Start *</Label>\n <Input\n type=\"date\"\n value={periodStart}\n onChange={(e) => setPeriodStart(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Period End *</Label>\n <Input type=\"date\" value={periodEnd} onChange={(e) => setPeriodEnd(e.target.value)} />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={saving}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving}>\n {saving ? 'Saving…' : 'Save Metric'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function EsgDashboardPage() {\n const [dialogOpen, setDialogOpen] = useState(false);\n const [category, setCategory] = useState<string>('ALL');\n\n const { data, loading, refetch } = useQuery<{ esgMetrics: EsgMetric[] }>(ESG_METRICS_QUERY, {\n fetchPolicy: 'cache-and-network',\n });\n\n const metrics = useMemo(() => data?.esgMetrics ?? [], [data?.esgMetrics]);\n\n const headline = useMemo(() => metrics.slice(0, 4), [metrics]);\n\n const filtered = useMemo(\n () => (category === 'ALL' ? metrics : metrics.filter((m) => m.category === category)),\n [metrics, category]\n );\n\n const categoryMix = useMemo<DonutSlice[]>(() => {\n const counts = metrics.reduce<Record<string, number>>((acc, m) => {\n acc[m.category] = (acc[m.category] ?? 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts).map(([cat, value], i) => ({\n name: cat.charAt(0).toUpperCase() + cat.slice(1),\n value,\n color: seriesColor(i),\n }));\n }, [metrics]);\n\n const valueVsTarget = useMemo(\n () =>\n metrics\n .filter((m) => m.target != null)\n .slice(0, 8)\n .map((m) => ({ name: m.metricName, value: m.value, target: m.target as number })),\n [metrics]\n );\n\n const categoryCounts = useMemo(() => {\n const counts: Record<string, number> = { ALL: metrics.length };\n for (const m of metrics) counts[m.category] = (counts[m.category] ?? 0) + 1;\n return counts;\n }, [metrics]);\n\n const pagination = usePagination(filtered, { pageSize: 8, resetKey: category });\n\n return (\n <div className=\"space-y-5 p-4 sm:p-6\">\n <SectionHeader\n hero\n icon={Leaf}\n title=\"ESG Dashboard\"\n description=\"Measure the environmental and governance impact of your estate against targets.\"\n actions={\n <Button className=\"w-full sm:w-auto\" onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n Save Metric\n </Button>\n }\n />\n <PagePurpose>\n Track energy use, carbon emissions and responsible disposal against targets — and produce\n the evidence ESG reporting and audits require.\n </PagePurpose>\n\n <RecordMetricDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onSaved={() => void refetch()}\n />\n\n {loading && metrics.length === 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-28 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer\" />\n ))}\n </div>\n ) : null}\n\n {!loading && metrics.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No ESG metrics recorded yet\"\n description=\"Save a metric to start tracking sustainability progress against your targets.\"\n />\n ) : null}\n\n {headline.length > 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {headline.map((m) => {\n const meta = TREND_META[m.trend ?? 'stable'] ?? TREND_META.stable;\n return (\n <StatTile\n key={m.id}\n label={`${m.metricName} (${m.unit})`}\n value={m.value}\n format={formatNumber}\n delta={meta.label}\n deltaDirection={meta.dir}\n deltaTone={meta.tone}\n icon={Leaf}\n />\n );\n })}\n </div>\n ) : null}\n\n {metrics.length > 0 ? (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <ChartCard title=\"Value vs. target\" subtitle=\"Metrics measured against their targets\">\n {valueVsTarget.length > 0 ? (\n <BarsChart\n data={valueVsTarget}\n xKey=\"name\"\n series={[\n { key: 'value', label: 'Value' },\n { key: 'target', label: 'Target' },\n ]}\n orientation=\"horizontal\"\n height={240}\n categoryWidth={150}\n ariaLabel=\"Metric value versus target\"\n />\n ) : (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-secondary\">\n Set a target on a metric to compare value against target.\n </div>\n )}\n </ChartCard>\n <ChartCard title=\"By category\" subtitle=\"Metrics across E / S / G\">\n <DonutChart\n data={categoryMix}\n height={240}\n centerLabel=\"Metrics\"\n centerValue={metrics.length}\n ariaLabel=\"Metrics by category\"\n />\n </ChartCard>\n </div>\n ) : null}\n\n {metrics.length > 0 ? (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n {(['ALL', ...ESG_CATEGORIES] as const).map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setCategory(c)}\n className={cn(\n 'rounded-full px-3.5 py-1.5 text-sm font-medium capitalize transition-colors',\n category === c\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {c === 'ALL' ? 'All' : c}\n <span className=\"ml-1.5 text-xs opacity-70 tabular-nums\">\n {categoryCounts[c] ?? 0}\n </span>\n </button>\n ))}\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {pagination.pageItems.map((metric) => {\n const pct =\n metric.target && metric.target > 0\n ? Math.min(100, Math.round((metric.value / metric.target) * 100))\n : null;\n return (\n <GlassCard key={metric.id} treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"space-y-3 p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">\n {metric.category}\n </p>\n <h2 className=\"mt-0.5 text-base font-semibold text-text-primary\">\n {metric.metricName}\n </h2>\n </div>\n <TrendBadge trend={metric.trend} />\n </div>\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatNumber(metric.value)}\n </span>\n <span className=\"text-sm text-text-secondary\">{metric.unit}</span>\n {metric.target != null ? (\n <span className=\"text-xs text-text-muted\">\n target {formatNumber(metric.target)}\n </span>\n ) : null}\n </div>\n {pct != null ? (\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Progress to target</span>\n <span className=\"font-medium tabular-nums\">{pct}%</span>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n ) : null}\n </div>\n </GlassCard>\n );\n })}\n </div>\n\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=\"metrics\"\n />\n </>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,521 @@
1
+ import { useAssetHandlerNavigate as e } from "../hooks/useAssetHandlerNavigate.js";
2
+ import { cn as t } from "../utils/cn.js";
3
+ import { notify as n } from "../utils/toast.js";
4
+ import { seriesColor as r } from "../components/charts/chartTheme.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";
10
+ 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";
17
+ //#region src/assethandler/pages/KnowledgeBasePage.tsx
18
+ var H = y`
19
+ query AssetHandlerKnowledgeArticles {
20
+ knowledgeArticles {
21
+ id
22
+ title
23
+ slug
24
+ summary
25
+ content
26
+ status
27
+ tags
28
+ views
29
+ createdBy
30
+ updatedBy
31
+ publishedAt
32
+ createdAt
33
+ updatedAt
34
+ relatedAssetId
35
+ relatedIncidentId
36
+ }
37
+ }
38
+ `, U = y`
39
+ mutation AssetHandlerCreateKnowledgeArticle($input: CreateKnowledgeArticleInput!) {
40
+ createKnowledgeArticle(input: $input) {
41
+ id
42
+ title
43
+ }
44
+ }
45
+ `, W = [
46
+ "DRAFT",
47
+ "PUBLISHED",
48
+ "ARCHIVED"
49
+ ], G = {
50
+ PUBLISHED: {
51
+ label: "Published",
52
+ className: "bg-status-success-bg-subtle text-status-success-text"
53
+ },
54
+ DRAFT: {
55
+ label: "Draft",
56
+ className: "bg-bg-sunken text-text-secondary"
57
+ },
58
+ ARCHIVED: {
59
+ label: "Archived",
60
+ className: "bg-status-warning-bg-subtle text-status-warning-text"
61
+ }
62
+ }, K = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), q = (e) => e ? new Date(e).toLocaleDateString(void 0, {
63
+ year: "numeric",
64
+ month: "short",
65
+ day: "numeric"
66
+ }) : "—";
67
+ function te(e) {
68
+ if (e.summary && e.summary.trim()) return e.summary.trim();
69
+ let t = e.content.replace(/\s+/g, " ").trim();
70
+ return t.length > 160 ? `${t.slice(0, 160)}…` : t;
71
+ }
72
+ function ne(e) {
73
+ return (e.replace(/[^a-zA-Z0-9]/g, "").slice(0, 2) || "?").toUpperCase();
74
+ }
75
+ function J({ status: e }) {
76
+ let n = G[e] ?? {
77
+ label: e,
78
+ className: "bg-bg-sunken text-text-secondary"
79
+ };
80
+ return /* @__PURE__ */ C("span", {
81
+ className: t("inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium", n.className),
82
+ children: n.label
83
+ });
84
+ }
85
+ function re({ open: e, onOpenChange: t, onPublished: r }) {
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
+ n.success("Article published", `${e.createKnowledgeArticle.title} is now available.`), y(), t(!1), r();
91
+ },
92
+ onError: (e) => n.error("Could not publish article", e.message)
93
+ }), E = (e) => {
94
+ a(e), c || s(K(e));
95
+ };
96
+ return /* @__PURE__ */ C(A, {
97
+ open: e,
98
+ onOpenChange: t,
99
+ children: /* @__PURE__ */ w(j, {
100
+ className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl",
101
+ 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
+ ]
163
+ }),
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
+ n.warning("Missing details", "Title and slug are required.");
173
+ return;
174
+ }
175
+ if (!p.trim()) {
176
+ n.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
+ })] })
192
+ ]
193
+ })
194
+ });
195
+ }
196
+ function ie({ article: t, onOpenChange: n }) {
197
+ let r = e();
198
+ return /* @__PURE__ */ C(A, {
199
+ open: !!t,
200
+ onOpenChange: n,
201
+ children: /* @__PURE__ */ C(j, {
202
+ className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl",
203
+ children: t ? /* @__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: t.status }), t.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: t.title
215
+ }),
216
+ /* @__PURE__ */ w(M, { children: [
217
+ "Updated ",
218
+ q(t.updatedAt),
219
+ " · ",
220
+ t.views.toLocaleString(),
221
+ " views"
222
+ ] })
223
+ ] }),
224
+ /* @__PURE__ */ C("div", {
225
+ className: "whitespace-pre-wrap text-sm leading-relaxed text-text-primary",
226
+ children: t.content
227
+ }),
228
+ t.relatedAssetId || t.relatedIncidentId ? /* @__PURE__ */ w("div", {
229
+ className: "mt-4 space-y-2 border-t border-border-seam pt-4",
230
+ children: [/* @__PURE__ */ w("h4", {
231
+ 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", {
234
+ className: "flex flex-wrap gap-2",
235
+ children: [t.relatedAssetId ? /* @__PURE__ */ w(T, {
236
+ variant: "outline",
237
+ size: "sm",
238
+ onClick: () => {
239
+ n(!1), r(`/assets/${t.relatedAssetId}`);
240
+ },
241
+ children: ["View linked asset", /* @__PURE__ */ C(p, { className: "ml-1.5 h-3.5 w-3.5" })]
242
+ }) : null, t.relatedIncidentId ? /* @__PURE__ */ w(T, {
243
+ variant: "outline",
244
+ size: "sm",
245
+ onClick: () => {
246
+ n(!1), r(`/itil/incidents/${t.relatedIncidentId}`);
247
+ },
248
+ children: ["View linked incident", /* @__PURE__ */ C(p, { className: "ml-1.5 h-3.5 w-3.5" })]
249
+ }) : null]
250
+ })]
251
+ }) : null
252
+ ] }) : null
253
+ })
254
+ });
255
+ }
256
+ function Y() {
257
+ let [e, n] = 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(() => {
258
+ 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);
260
+ 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 ? [
264
+ t.title,
265
+ t.summary ?? "",
266
+ t.content,
267
+ 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());
269
+ }, [
270
+ K,
271
+ p,
272
+ y,
273
+ S,
274
+ j
275
+ ]), Z = u(() => {
276
+ let e = K.reduce((e, t) => (e[t.status] = (e[t.status] ?? 0) + 1, e), {});
277
+ return Object.entries(e).map(([e, t], n) => ({
278
+ name: G[e]?.label ?? e,
279
+ value: t,
280
+ color: r(n)
281
+ }));
282
+ }, [K]), Q = u(() => [...K].filter((e) => e.views > 0).sort((e, t) => t.views - e.views).slice(0, 6).map((e) => ({
283
+ name: e.title,
284
+ views: e.views
285
+ })), [K]), $ = s(X, {
286
+ pageSize: 9,
287
+ resetKey: `${p}|${y}|${S}|${j}`
288
+ });
289
+ return /* @__PURE__ */ w("div", {
290
+ className: "space-y-5 p-4 sm:p-6",
291
+ children: [
292
+ /* @__PURE__ */ C(l, {
293
+ hero: !0,
294
+ icon: f,
295
+ title: "Knowledge Base",
296
+ description: "Turn one-off fixes and runbooks into repeatable, searchable operational knowledge.",
297
+ actions: /* @__PURE__ */ w(T, {
298
+ className: "w-full sm:w-auto",
299
+ onClick: () => n(!0),
300
+ children: [/* @__PURE__ */ C(g, { className: "mr-2 h-4 w-4" }), "New Article"]
301
+ })
302
+ }),
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: e,
306
+ onOpenChange: n,
307
+ onPublished: () => void U()
308
+ }),
309
+ /* @__PURE__ */ C(ie, {
310
+ article: N,
311
+ onOpenChange: (e) => !e && P(null)
312
+ }),
313
+ /* @__PURE__ */ w("div", {
314
+ 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, {
316
+ className: "h-12 pl-11 text-base",
317
+ placeholder: "Search runbooks, fixes and procedures…",
318
+ value: p,
319
+ onChange: (e) => h(e.target.value),
320
+ "aria-label": "Search knowledge articles"
321
+ })]
322
+ }),
323
+ K.length > 0 && (Z.length > 0 || Q.length > 0) ? /* @__PURE__ */ w("div", {
324
+ className: "grid gap-4 lg:grid-cols-2",
325
+ children: [/* @__PURE__ */ C(c, {
326
+ title: "Publishing status",
327
+ subtitle: "Articles by status",
328
+ children: /* @__PURE__ */ C(a, {
329
+ data: Z,
330
+ height: 200,
331
+ centerLabel: "Articles",
332
+ centerValue: K.length,
333
+ ariaLabel: "Articles by status"
334
+ })
335
+ }), /* @__PURE__ */ C(c, {
336
+ title: "Most consulted",
337
+ subtitle: "Top articles by views",
338
+ children: /* @__PURE__ */ C(i, {
339
+ data: Q,
340
+ xKey: "name",
341
+ series: [{
342
+ key: "views",
343
+ label: "Views"
344
+ }],
345
+ orientation: "horizontal",
346
+ height: 200,
347
+ categoryWidth: 150,
348
+ emptyLabel: "No views recorded yet",
349
+ ariaLabel: "Top articles by views"
350
+ })
351
+ })]
352
+ }) : null,
353
+ /* @__PURE__ */ w("div", {
354
+ className: "grid gap-5 lg:grid-cols-[200px_1fr]",
355
+ children: [/* @__PURE__ */ w("aside", {
356
+ className: "space-y-3",
357
+ children: [
358
+ /* @__PURE__ */ w("div", {
359
+ className: "flex items-center justify-between gap-2",
360
+ children: [/* @__PURE__ */ C("h3", {
361
+ className: "text-sm font-semibold text-text-primary",
362
+ children: "Category"
363
+ }), /* @__PURE__ */ w(I, {
364
+ value: j,
365
+ onValueChange: (e) => M(e),
366
+ children: [/* @__PURE__ */ C(z, {
367
+ className: "h-8 w-[130px] text-xs lg:hidden",
368
+ children: /* @__PURE__ */ C(B, {})
369
+ }), /* @__PURE__ */ w(L, { children: [/* @__PURE__ */ C(R, {
370
+ value: "recent",
371
+ children: "Recently updated"
372
+ }), /* @__PURE__ */ C(R, {
373
+ value: "views",
374
+ children: "Most viewed"
375
+ })] })]
376
+ })]
377
+ }),
378
+ /* @__PURE__ */ w("div", {
379
+ className: "flex gap-2 overflow-x-auto pb-1 lg:flex-col lg:overflow-visible lg:pb-0",
380
+ children: [/* @__PURE__ */ w("button", {
381
+ type: "button",
382
+ onClick: () => b("ALL"),
383
+ className: t("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", {
385
+ className: "ml-1.5 text-xs text-text-muted tabular-nums",
386
+ children: K.length
387
+ })]
388
+ }), Y.map(([e, n]) => /* @__PURE__ */ w("button", {
389
+ type: "button",
390
+ onClick: () => b(e),
391
+ className: t("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", {
393
+ className: "truncate",
394
+ children: e
395
+ }), /* @__PURE__ */ C("span", {
396
+ className: "text-xs text-text-muted tabular-nums",
397
+ children: n
398
+ })]
399
+ }, e))]
400
+ }),
401
+ /* @__PURE__ */ w("div", {
402
+ className: "hidden space-y-1.5 border-t border-border-seam pt-3 lg:block",
403
+ children: [
404
+ /* @__PURE__ */ C(k, {
405
+ className: "text-xs",
406
+ children: "Status"
407
+ }),
408
+ /* @__PURE__ */ w(I, {
409
+ value: S,
410
+ onValueChange: A,
411
+ children: [/* @__PURE__ */ C(z, {
412
+ className: "h-9",
413
+ children: /* @__PURE__ */ C(B, {})
414
+ }), /* @__PURE__ */ w(L, { children: [/* @__PURE__ */ C(R, {
415
+ value: "ALL",
416
+ children: "All statuses"
417
+ }), W.map((e) => /* @__PURE__ */ C(R, {
418
+ value: e,
419
+ children: e
420
+ }, e))] })]
421
+ }),
422
+ /* @__PURE__ */ C(k, {
423
+ className: "text-xs",
424
+ children: "Sort"
425
+ }),
426
+ /* @__PURE__ */ w(I, {
427
+ value: j,
428
+ onValueChange: (e) => M(e),
429
+ children: [/* @__PURE__ */ C(z, {
430
+ className: "h-9",
431
+ children: /* @__PURE__ */ C(B, {})
432
+ }), /* @__PURE__ */ w(L, { children: [/* @__PURE__ */ C(R, {
433
+ value: "recent",
434
+ children: "Recently updated"
435
+ }), /* @__PURE__ */ C(R, {
436
+ value: "views",
437
+ children: "Most viewed"
438
+ })] })]
439
+ })
440
+ ]
441
+ })
442
+ ]
443
+ }), /* @__PURE__ */ w("div", { children: [
444
+ V && K.length === 0 ? /* @__PURE__ */ C("div", {
445
+ 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))
447
+ }) : 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."
452
+ }) : null,
453
+ /* @__PURE__ */ C("div", {
454
+ className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3",
455
+ children: $.pageItems.map((e) => /* @__PURE__ */ C(E, {
456
+ treatment: "glass",
457
+ className: "border-border-seam",
458
+ children: /* @__PURE__ */ w("button", {
459
+ type: "button",
460
+ onClick: () => P(e),
461
+ className: "flex h-full w-full flex-col gap-3 p-4 text-left",
462
+ children: [
463
+ /* @__PURE__ */ w("div", {
464
+ 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", {
466
+ className: "rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-primary",
467
+ children: e
468
+ }, e))]
469
+ }),
470
+ /* @__PURE__ */ C("h2", {
471
+ className: "line-clamp-2 text-base font-semibold text-text-primary",
472
+ children: e.title
473
+ }),
474
+ /* @__PURE__ */ C("p", {
475
+ className: "line-clamp-2 flex-1 text-sm text-text-secondary",
476
+ children: te(e)
477
+ }),
478
+ /* @__PURE__ */ w("div", {
479
+ className: "flex items-center justify-between gap-2 border-t border-border-seam/60 pt-3",
480
+ children: [/* @__PURE__ */ w("div", {
481
+ className: "flex min-w-0 items-center gap-2",
482
+ children: [/* @__PURE__ */ C("span", {
483
+ 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", {
486
+ className: "truncate text-xs text-text-secondary",
487
+ children: ["Updated ", q(e.updatedAt)]
488
+ })]
489
+ }), /* @__PURE__ */ w("span", {
490
+ 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()]
492
+ })]
493
+ })
494
+ ]
495
+ })
496
+ }, e.id))
497
+ }),
498
+ /* @__PURE__ */ C("div", {
499
+ 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,
510
+ itemLabel: "articles"
511
+ })
512
+ })
513
+ ] })]
514
+ })
515
+ ]
516
+ });
517
+ }
518
+ //#endregion
519
+ export { Y as KnowledgeBasePage };
520
+
521
+ //# sourceMappingURL=KnowledgeBasePage.js.map