@burdenoff/microfe-planmagnet 2026.1001.1 → 2026.1001.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +156 -150
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +19 -15
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/comments/CommentThread.js +36 -36
- package/dist/planmagnet/comments/RoadmapComments.js +21 -21
- package/dist/planmagnet/controls/AutomationRuleControls.js +24 -24
- package/dist/planmagnet/controls/FeatureControls.js +60 -60
- package/dist/planmagnet/controls/SyncControls.js +59 -59
- package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js +15 -15
- package/dist/planmagnet/docs/DocSuggestionsPanel.js +12 -12
- package/dist/planmagnet/docs/DocVersionCompare.js +3 -3
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts +12 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js +208 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js +190 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +99 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts +8 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js +185 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts +35 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.js +37 -0
- package/dist/planmagnet/forecast/riskFlags.js.map +1 -0
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +216 -182
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +11 -11
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +163 -163
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +165 -165
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +234 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +206 -182
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportPagePage.js +22 -22
- package/dist/planmagnet/pages/ReportsReportPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +21 -21
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +88 -83
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +139 -112
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js.map +1 -1
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +21 -21
- package/dist/planmagnet/product/ProductControls.js +91 -91
- package/dist/planmagnet/reports/SchedulesPanel.js +33 -33
- package/dist/planmagnet/reports/ShareDialog.js +20 -20
- package/dist/planmagnet/reports/ShareDialog.js.map +1 -1
- package/dist/planmagnet/reports/sharedReportRoute.d.ts +3 -2
- package/dist/planmagnet/reports/sharedReportRoute.d.ts.map +1 -1
- package/dist/planmagnet/reports/sharedReportRoute.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +60 -60
- package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js +30 -30
- package/dist/planmagnet/roadmaps/RoadmapReviewPanel.js +21 -21
- package/dist/planmagnet/roadmaps/RoadmapVersionsPanel.js +22 -22
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { PersonName as t } from "../people/People.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { mutationErrorMessage as n } from "../utils/graphqlErrors.js";
|
|
4
|
+
import { throwOnError as r } from "../utils/mutationResult.js";
|
|
5
|
+
import { ConfirmButton as i, ErrorLine as a } from "../controls/ConfirmButton.js";
|
|
6
6
|
import { CREATE_REPORT_SCHEDULE as o, DELETE_REPORT_SCHEDULE as s, LIST_REPORT_SCHEDULES as c, UPDATE_REPORT_SCHEDULE as l } from "./reportGraphql.js";
|
|
7
7
|
import { useDeferredValue as u, useEffect as d, useState as f } from "react";
|
|
8
8
|
import { CalendarClock as p, Plus as m } from "lucide-react";
|
|
@@ -106,7 +106,7 @@ function W(e) {
|
|
|
106
106
|
recipientIds: /* @__PURE__ */ new Set()
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
|
-
function G({ reportId: e, schedule: t, onCancel:
|
|
109
|
+
function G({ reportId: e, schedule: t, onCancel: i, onSaved: s }) {
|
|
110
110
|
let [u, d] = f(() => W(t)), [p, m] = f(null), g = { refetchQueries: [{
|
|
111
111
|
query: c,
|
|
112
112
|
variables: { reportId: e }
|
|
@@ -118,7 +118,7 @@ function G({ reportId: e, schedule: t, onCancel: r, onSaved: s }) {
|
|
|
118
118
|
};
|
|
119
119
|
}), H = async () => {
|
|
120
120
|
m(null);
|
|
121
|
-
let
|
|
121
|
+
let i = {
|
|
122
122
|
cadence: u.cadence,
|
|
123
123
|
hourUtc: u.hourUtc,
|
|
124
124
|
dayOfWeek: u.cadence === "WEEKLY" ? u.dayOfWeek : void 0,
|
|
@@ -127,15 +127,15 @@ function G({ reportId: e, schedule: t, onCancel: r, onSaved: s }) {
|
|
|
127
127
|
recipientIds: [...u.recipientIds]
|
|
128
128
|
};
|
|
129
129
|
try {
|
|
130
|
-
t ? await
|
|
130
|
+
t ? await r(T({ variables: {
|
|
131
131
|
id: t.id,
|
|
132
|
-
input:
|
|
133
|
-
} })) : await
|
|
132
|
+
input: i
|
|
133
|
+
} })) : await r(b({ variables: {
|
|
134
134
|
reportId: e,
|
|
135
|
-
input:
|
|
135
|
+
input: i
|
|
136
136
|
} })), s();
|
|
137
137
|
} catch (e) {
|
|
138
|
-
m(
|
|
138
|
+
m(n(e, "The schedule could not be saved."));
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
141
|
return /* @__PURE__ */ y(_, { children: [
|
|
@@ -259,10 +259,10 @@ function G({ reportId: e, schedule: t, onCancel: r, onSaved: s }) {
|
|
|
259
259
|
})] })
|
|
260
260
|
]
|
|
261
261
|
}),
|
|
262
|
-
p && /* @__PURE__ */ v(
|
|
262
|
+
p && /* @__PURE__ */ v(a, { message: p }),
|
|
263
263
|
/* @__PURE__ */ y(O, { children: [/* @__PURE__ */ v(x, {
|
|
264
264
|
variant: "outline",
|
|
265
|
-
onClick:
|
|
265
|
+
onClick: i,
|
|
266
266
|
children: "Cancel"
|
|
267
267
|
}), /* @__PURE__ */ v(x, {
|
|
268
268
|
onClick: () => void H(),
|
|
@@ -287,7 +287,7 @@ function K({ reportId: e, schedule: t, open: n, onOpenChange: r }) {
|
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
289
|
function q({ schedule: e, reportId: t }) {
|
|
290
|
-
let [
|
|
290
|
+
let [i, { loading: o }] = h(l, { refetchQueries: [{
|
|
291
291
|
query: c,
|
|
292
292
|
variables: { reportId: t }
|
|
293
293
|
}] }), [s, u] = f(null);
|
|
@@ -301,12 +301,12 @@ function q({ schedule: e, reportId: t }) {
|
|
|
301
301
|
onCheckedChange: (t) => {
|
|
302
302
|
u(null), (async () => {
|
|
303
303
|
try {
|
|
304
|
-
await
|
|
304
|
+
await r(i({ variables: {
|
|
305
305
|
id: e.id,
|
|
306
306
|
input: { active: t }
|
|
307
307
|
} }));
|
|
308
308
|
} catch (e) {
|
|
309
|
-
u(
|
|
309
|
+
u(n(e, "Could not update."));
|
|
310
310
|
}
|
|
311
311
|
})();
|
|
312
312
|
}
|
|
@@ -315,7 +315,7 @@ function q({ schedule: e, reportId: t }) {
|
|
|
315
315
|
className: "text-xs text-muted-foreground",
|
|
316
316
|
children: e.active ? "Active" : "Paused"
|
|
317
317
|
}),
|
|
318
|
-
s && /* @__PURE__ */ v(
|
|
318
|
+
s && /* @__PURE__ */ v(a, { message: s })
|
|
319
319
|
]
|
|
320
320
|
});
|
|
321
321
|
}
|
|
@@ -345,7 +345,7 @@ function J({ reportId: e }) {
|
|
|
345
345
|
className: "text-xs text-muted-foreground",
|
|
346
346
|
children: "Email delivery is not available yet (Linear BOFF-7927) — recipients read a scheduled run in-app, under run history."
|
|
347
347
|
}),
|
|
348
|
-
T && /* @__PURE__ */ v(
|
|
348
|
+
T && /* @__PURE__ */ v(a, { message: T }),
|
|
349
349
|
C && !S ? /* @__PURE__ */ v("p", {
|
|
350
350
|
className: "text-sm text-muted-foreground",
|
|
351
351
|
children: "Loading…"
|
|
@@ -354,7 +354,7 @@ function J({ reportId: e }) {
|
|
|
354
354
|
children: "No schedules yet."
|
|
355
355
|
}) : /* @__PURE__ */ v("ul", {
|
|
356
356
|
className: "space-y-2",
|
|
357
|
-
children: D.map((
|
|
357
|
+
children: D.map((a) => /* @__PURE__ */ y("li", {
|
|
358
358
|
className: "rounded-md border border-border p-3 text-sm",
|
|
359
359
|
children: [
|
|
360
360
|
/* @__PURE__ */ y("div", {
|
|
@@ -369,36 +369,36 @@ function J({ reportId: e }) {
|
|
|
369
369
|
/* @__PURE__ */ y("span", {
|
|
370
370
|
className: "font-medium",
|
|
371
371
|
children: [
|
|
372
|
-
|
|
372
|
+
a.cadence.charAt(0) + a.cadence.slice(1).toLowerCase(),
|
|
373
373
|
" at",
|
|
374
374
|
" ",
|
|
375
|
-
String(
|
|
375
|
+
String(a.hourUtc).padStart(2, "0"),
|
|
376
376
|
":00 UTC (",
|
|
377
|
-
V(
|
|
377
|
+
V(a.hourUtc),
|
|
378
378
|
")"
|
|
379
379
|
]
|
|
380
380
|
}),
|
|
381
381
|
/* @__PURE__ */ v(b, {
|
|
382
382
|
variant: "outline",
|
|
383
|
-
children:
|
|
383
|
+
children: a.format
|
|
384
384
|
})
|
|
385
385
|
]
|
|
386
386
|
}), /* @__PURE__ */ y("div", {
|
|
387
387
|
className: "flex items-center gap-2",
|
|
388
388
|
children: [
|
|
389
389
|
/* @__PURE__ */ v(q, {
|
|
390
|
-
schedule:
|
|
390
|
+
schedule: a,
|
|
391
391
|
reportId: e
|
|
392
392
|
}),
|
|
393
393
|
/* @__PURE__ */ v(x, {
|
|
394
394
|
size: "sm",
|
|
395
395
|
variant: "outline",
|
|
396
396
|
onClick: () => {
|
|
397
|
-
d(
|
|
397
|
+
d(a), l(!0);
|
|
398
398
|
},
|
|
399
399
|
children: "Edit"
|
|
400
400
|
}),
|
|
401
|
-
/* @__PURE__ */ v(
|
|
401
|
+
/* @__PURE__ */ v(i, {
|
|
402
402
|
label: "Delete",
|
|
403
403
|
icon: null,
|
|
404
404
|
title: "Delete this schedule?",
|
|
@@ -410,9 +410,9 @@ function J({ reportId: e }) {
|
|
|
410
410
|
onConfirm: () => {
|
|
411
411
|
E(null), (async () => {
|
|
412
412
|
try {
|
|
413
|
-
await
|
|
413
|
+
await r(w({ variables: { id: a.id } }));
|
|
414
414
|
} catch (e) {
|
|
415
|
-
E(
|
|
415
|
+
E(n(e, "Could not delete."));
|
|
416
416
|
}
|
|
417
417
|
})();
|
|
418
418
|
}
|
|
@@ -424,23 +424,23 @@ function J({ reportId: e }) {
|
|
|
424
424
|
className: "mt-1 text-xs text-muted-foreground",
|
|
425
425
|
children: [
|
|
426
426
|
"Next run ",
|
|
427
|
-
H(
|
|
427
|
+
H(a.nextRunAt),
|
|
428
428
|
" · Last run",
|
|
429
429
|
" ",
|
|
430
|
-
H(
|
|
431
|
-
|
|
430
|
+
H(a.lastRunAt),
|
|
431
|
+
a.lastError ? ` · last error: ${a.lastError}` : ""
|
|
432
432
|
]
|
|
433
433
|
}),
|
|
434
434
|
/* @__PURE__ */ v("p", {
|
|
435
435
|
className: "mt-1 text-xs text-muted-foreground",
|
|
436
|
-
children:
|
|
436
|
+
children: a.recipientIds.length === 0 ? "No recipients" : /* @__PURE__ */ y(_, { children: [
|
|
437
437
|
"Recipients:",
|
|
438
438
|
" ",
|
|
439
|
-
|
|
439
|
+
a.recipientIds.map((e, n) => /* @__PURE__ */ y("span", { children: [n > 0 && ", ", /* @__PURE__ */ v(t, { id: e })] }, e))
|
|
440
440
|
] })
|
|
441
441
|
})
|
|
442
442
|
]
|
|
443
|
-
},
|
|
443
|
+
}, a.id))
|
|
444
444
|
}),
|
|
445
445
|
/* @__PURE__ */ v(K, {
|
|
446
446
|
reportId: e,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { mutationErrorMessage as e } from "../utils/graphqlErrors.js";
|
|
2
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
|
+
import { ConfirmButton as n, ErrorLine as r } from "../controls/ConfirmButton.js";
|
|
4
4
|
import { sharedReportLink as i } from "./sharedReportRoute.js";
|
|
5
5
|
import { CREATE_REPORT_SHARE as a, LIST_REPORT_SHARES as o, REVOKE_REPORT_SHARE as s } from "./reportGraphql.js";
|
|
6
6
|
import { useState as c } from "react";
|
|
7
7
|
import { Check as l, Copy as u, Share2 as d } from "lucide-react";
|
|
8
8
|
import { useMutation as f, useQuery as p } from "@apollo/client/react";
|
|
9
9
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
10
|
-
import { Badge as g, Button as _, Input as v, Label as y, ResponsiveDialog as b, ResponsiveDialogContent as
|
|
10
|
+
import { Badge as g, Button as _, Input as v, Label as y, ResponsiveDialog as b, ResponsiveDialogContent as x, ResponsiveDialogDescription as S, ResponsiveDialogFooter as C, ResponsiveDialogHeader as ee, ResponsiveDialogTitle as w, ResponsiveDialogTrigger as T } from "@burdenoff/fe-libs/ui";
|
|
11
11
|
import { copyTextToClipboard as E } from "@burdenoff/fe-libs/sharing";
|
|
12
12
|
//#region src/planmagnet/reports/ShareDialog.tsx
|
|
13
13
|
var D = 864e5, O = 7, k = 90;
|
|
@@ -31,7 +31,7 @@ function M({ reportId: D, runId: M }) {
|
|
|
31
31
|
if (M) {
|
|
32
32
|
H(null), W(null);
|
|
33
33
|
try {
|
|
34
|
-
let
|
|
34
|
+
let e = (await t(J({ variables: {
|
|
35
35
|
runId: M,
|
|
36
36
|
input: {
|
|
37
37
|
expiresAt: `${F}T23:59:59.999Z`,
|
|
@@ -39,17 +39,17 @@ function M({ reportId: D, runId: M }) {
|
|
|
39
39
|
watermark: z.trim() || void 0
|
|
40
40
|
}
|
|
41
41
|
} }))).data?.planmagnetCreateReportShare;
|
|
42
|
-
|
|
43
|
-
} catch (
|
|
44
|
-
H(
|
|
42
|
+
e && (W(e), K(!1), R(""), B(""));
|
|
43
|
+
} catch (t) {
|
|
44
|
+
H(e(t, "The share link could not be created."));
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
}, Q = async (
|
|
47
|
+
}, Q = async (n) => {
|
|
48
48
|
H(null);
|
|
49
49
|
try {
|
|
50
|
-
await
|
|
51
|
-
} catch (
|
|
52
|
-
H(
|
|
50
|
+
await t(X({ variables: { id: n } })), U?.id === n && W(null);
|
|
51
|
+
} catch (t) {
|
|
52
|
+
H(e(t, "The share could not be revoked."));
|
|
53
53
|
}
|
|
54
54
|
}, te = async (e) => {
|
|
55
55
|
try {
|
|
@@ -71,10 +71,10 @@ function M({ reportId: D, runId: M }) {
|
|
|
71
71
|
"data-testid": "report-share",
|
|
72
72
|
children: [/* @__PURE__ */ m(d, { className: "mr-1 h-4 w-4" }), "Share"]
|
|
73
73
|
})
|
|
74
|
-
}), /* @__PURE__ */ h(
|
|
74
|
+
}), /* @__PURE__ */ h(x, {
|
|
75
75
|
className: "max-h-[85vh] overflow-y-auto sm:max-w-lg",
|
|
76
76
|
children: [
|
|
77
|
-
/* @__PURE__ */ h(
|
|
77
|
+
/* @__PURE__ */ h(ee, { children: [/* @__PURE__ */ m(w, { children: "Share this report" }), /* @__PURE__ */ m(S, { children: "Anyone with the link can view the most recently shared run, read-only, without signing in." })] }),
|
|
78
78
|
!M && /* @__PURE__ */ m("p", {
|
|
79
79
|
className: "text-sm text-muted-foreground",
|
|
80
80
|
children: "Run this report at least once before sharing it — a share links to one specific run."
|
|
@@ -151,7 +151,7 @@ function M({ reportId: D, runId: M }) {
|
|
|
151
151
|
})
|
|
152
152
|
]
|
|
153
153
|
}),
|
|
154
|
-
V && /* @__PURE__ */ m(
|
|
154
|
+
V && /* @__PURE__ */ m(r, { message: V }),
|
|
155
155
|
/* @__PURE__ */ h("div", {
|
|
156
156
|
className: "space-y-2",
|
|
157
157
|
"data-testid": "report-shares",
|
|
@@ -167,7 +167,7 @@ function M({ reportId: D, runId: M }) {
|
|
|
167
167
|
}) : /* @__PURE__ */ m("ul", {
|
|
168
168
|
className: "space-y-2",
|
|
169
169
|
children: $.map((e) => {
|
|
170
|
-
let
|
|
170
|
+
let t = !!e.revokedAt, r = !t && new Date(e.expiresAt).getTime() < (/* @__PURE__ */ new Date()).getTime();
|
|
171
171
|
return /* @__PURE__ */ h("li", {
|
|
172
172
|
className: "flex flex-wrap items-center justify-between gap-2 rounded-md border border-border p-2 text-xs",
|
|
173
173
|
children: [/* @__PURE__ */ h("div", {
|
|
@@ -176,8 +176,8 @@ function M({ reportId: D, runId: M }) {
|
|
|
176
176
|
/* @__PURE__ */ h("div", {
|
|
177
177
|
className: "flex items-center gap-1.5",
|
|
178
178
|
children: [/* @__PURE__ */ m(g, {
|
|
179
|
-
variant:
|
|
180
|
-
children:
|
|
179
|
+
variant: t ? "secondary" : r ? "warning" : "success",
|
|
180
|
+
children: t ? "Revoked" : r ? "Expired" : "Active"
|
|
181
181
|
}), e.hasPassword && /* @__PURE__ */ m(g, {
|
|
182
182
|
variant: "outline",
|
|
183
183
|
children: "Password"
|
|
@@ -204,7 +204,7 @@ function M({ reportId: D, runId: M }) {
|
|
|
204
204
|
]
|
|
205
205
|
})
|
|
206
206
|
]
|
|
207
|
-
}), !
|
|
207
|
+
}), !t && /* @__PURE__ */ m(n, {
|
|
208
208
|
label: "Revoke",
|
|
209
209
|
icon: null,
|
|
210
210
|
title: "Revoke this share?",
|
|
@@ -219,7 +219,7 @@ function M({ reportId: D, runId: M }) {
|
|
|
219
219
|
})
|
|
220
220
|
})]
|
|
221
221
|
}),
|
|
222
|
-
/* @__PURE__ */ m(
|
|
222
|
+
/* @__PURE__ */ m(C, { children: /* @__PURE__ */ m(_, {
|
|
223
223
|
variant: "outline",
|
|
224
224
|
onClick: () => P(!1),
|
|
225
225
|
children: "Close"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShareDialog.js","names":[],"sources":["../../../src/planmagnet/reports/ShareDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n Input,\n Label,\n} from '@burdenoff/fe-libs/ui';\nimport { Check, Copy, Share2 } from 'lucide-react';\nimport { copyTextToClipboard } from '@burdenoff/fe-libs/sharing';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport { mutationErrorMessage } from '../utils/graphqlErrors';\nimport { throwOnError } from '../utils/mutationResult';\nimport { CREATE_REPORT_SHARE, LIST_REPORT_SHARES, REVOKE_REPORT_SHARE } from './reportGraphql';\nimport { sharedReportLink } from './sharedReportRoute';\nimport type { ReportShare } from './reportModel';\n\n/** Share links (ANR-16), owner-only — a report's owner creates a link to one persisted run; anyone with the\n * link (and the password, if set) can view that run's snapshot read-only at `/public/report/<token>`, no\n * workspace login required. The raw token is shown exactly once, right after creation. */\n\nconst DAY_MS = 86_400_000;\nconst DEFAULT_EXPIRY_DAYS = 7;\nconst MAX_EXPIRY_DAYS = 90;\n\nfunction dateInputValue(daysFromNow: number): string {\n return new Date(Date.now() + daysFromNow * DAY_MS).toISOString().slice(0, 10);\n}\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleString();\n}\n\nexport function ShareDialog({ reportId, runId }: { reportId: string; runId: string | null }) {\n const [open, setOpen] = useState(false);\n const [expiresAt, setExpiresAt] = useState(() => dateInputValue(DEFAULT_EXPIRY_DAYS));\n const [password, setPassword] = useState('');\n const [watermark, setWatermark] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [justCreated, setJustCreated] = useState<ReportShare | null>(null);\n const [copied, setCopied] = useState(false);\n\n const shares = useQuery<{ planmagnetReportShares: ReportShare[] }>(LIST_REPORT_SHARES, {\n variables: { reportId },\n skip: !open,\n });\n const [createShare, createState] = useMutation(CREATE_REPORT_SHARE, {\n refetchQueries: [{ query: LIST_REPORT_SHARES, variables: { reportId } }],\n });\n const [revokeShare] = useMutation(REVOKE_REPORT_SHARE, {\n refetchQueries: [{ query: LIST_REPORT_SHARES, variables: { reportId } }],\n });\n\n const handleCreate = async () => {\n if (!runId) return;\n setError(null);\n setJustCreated(null);\n try {\n const result = await throwOnError(\n createShare({\n variables: {\n runId,\n input: {\n expiresAt: `${expiresAt}T23:59:59.999Z`,\n password: password.trim() || undefined,\n watermark: watermark.trim() || undefined,\n },\n },\n })\n );\n const share = (result.data as { planmagnetCreateReportShare?: ReportShare } | undefined)\n ?.planmagnetCreateReportShare;\n if (share) {\n setJustCreated(share);\n setCopied(false);\n setPassword('');\n setWatermark('');\n }\n } catch (e) {\n setError(mutationErrorMessage(e, 'The share link could not be created.'));\n }\n };\n\n const handleRevoke = async (id: string) => {\n setError(null);\n try {\n await throwOnError(revokeShare({ variables: { id } }));\n if (justCreated?.id === id) setJustCreated(null);\n } catch (e) {\n setError(mutationErrorMessage(e, 'The share could not be revoked.'));\n }\n };\n\n const copyLink = async (token: string) => {\n try {\n await copyTextToClipboard(sharedReportLink(token));\n setCopied(true);\n } catch {\n setError('Could not copy — copy the link manually.');\n }\n };\n\n const list = shares.data?.planmagnetReportShares ?? [];\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) {\n setJustCreated(null);\n setError(null);\n }\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"report-share\">\n <Share2 className=\"mr-1 h-4 w-4\" />\n Share\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Share this report</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Anyone with the link can view the most recently shared run, read-only, without signing\n in.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n {!runId && (\n <p className=\"text-sm text-muted-foreground\">\n Run this report at least once before sharing it — a share links to one specific run.\n </p>\n )}\n\n {runId && (\n <div className=\"space-y-3 rounded-md border border-border p-3\">\n <div>\n <Label htmlFor=\"share-expiry\">Expires</Label>\n <Input\n id=\"share-expiry\"\n type=\"date\"\n className=\"mt-1\"\n value={expiresAt}\n min={dateInputValue(1)}\n max={dateInputValue(MAX_EXPIRY_DAYS)}\n onChange={(e) => setExpiresAt(e.target.value)}\n />\n </div>\n <div>\n <Label htmlFor=\"share-password\">Password (optional)</Label>\n <Input\n id=\"share-password\"\n type=\"password\"\n className=\"mt-1\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Leave blank for no password\"\n />\n </div>\n <div>\n <Label htmlFor=\"share-watermark\">Watermark text (optional)</Label>\n <Input\n id=\"share-watermark\"\n className=\"mt-1\"\n value={watermark}\n onChange={(e) => setWatermark(e.target.value)}\n placeholder={`Shared via PlanMagnet on ${new Date().toLocaleDateString()}`}\n />\n </div>\n <Button\n size=\"sm\"\n onClick={handleCreate}\n disabled={createState.loading}\n data-testid=\"report-share-create\"\n >\n {createState.loading ? 'Creating…' : 'Create share link'}\n </Button>\n </div>\n )}\n\n {justCreated?.token && (\n <div\n className=\"space-y-2 rounded-md border border-primary bg-primary/5 p-3\"\n data-testid=\"report-share-link\"\n >\n <p className=\"text-sm font-medium\">Link created — copy it now.</p>\n <p className=\"text-xs text-muted-foreground\">\n You will not see this link again. If you lose it, revoke this share and create a new\n one.\n </p>\n <div className=\"flex items-center gap-2\">\n <Input readOnly value={sharedReportLink(justCreated.token)} className=\"text-xs\" />\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => copyLink(justCreated.token as string)}\n >\n {copied ? <Check className=\"h-4 w-4\" /> : <Copy className=\"h-4 w-4\" />}\n {copied ? 'Copied' : 'Copy'}\n </Button>\n </div>\n </div>\n )}\n\n {error && <ErrorLine message={error} />}\n\n <div className=\"space-y-2\" data-testid=\"report-shares\">\n <h3 className=\"text-sm font-semibold\">Existing shares</h3>\n {shares.loading && !shares.data ? (\n <p className=\"text-sm text-muted-foreground\">Loading…</p>\n ) : list.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">No shares yet.</p>\n ) : (\n <ul className=\"space-y-2\">\n {list.map((share) => {\n const revoked = !!share.revokedAt;\n const expired =\n !revoked && new Date(share.expiresAt).getTime() < new Date().getTime();\n return (\n <li\n key={share.id}\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-md border border-border p-2 text-xs\"\n >\n <div className=\"space-y-0.5\">\n <div className=\"flex items-center gap-1.5\">\n <Badge variant={revoked ? 'secondary' : expired ? 'warning' : 'success'}>\n {revoked ? 'Revoked' : expired ? 'Expired' : 'Active'}\n </Badge>\n {share.hasPassword && <Badge variant=\"outline\">Password</Badge>}\n </div>\n <p className=\"text-muted-foreground\">\n Created {formatDate(share.createdAt)} · Expires{' '}\n {formatDate(share.expiresAt)}\n </p>\n <p className=\"text-muted-foreground\">\n Accessed {share.accessCount} time{share.accessCount === 1 ? '' : 's'}\n {share.lastAccessedAt ? ` · last ${formatDate(share.lastAccessedAt)}` : ''}\n </p>\n </div>\n {!revoked && (\n <ConfirmButton\n label=\"Revoke\"\n icon={null}\n title=\"Revoke this share?\"\n description=\"Anyone with this link loses access immediately. This cannot be undone.\"\n confirmLabel=\"Revoke\"\n destructive\n variant=\"outline\"\n disabled={false}\n onConfirm={() => void handleRevoke(share.id)}\n />\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Close\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ShareDialog.js","names":[],"sources":["../../../src/planmagnet/reports/ShareDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n Input,\n Label,\n} from '@burdenoff/fe-libs/ui';\nimport { Check, Copy, Share2 } from 'lucide-react';\nimport { copyTextToClipboard } from '@burdenoff/fe-libs/sharing';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport { mutationErrorMessage } from '../utils/graphqlErrors';\nimport { throwOnError } from '../utils/mutationResult';\nimport { CREATE_REPORT_SHARE, LIST_REPORT_SHARES, REVOKE_REPORT_SHARE } from './reportGraphql';\nimport { sharedReportLink } from './sharedReportRoute';\nimport type { ReportShare } from './reportModel';\n\n/** Share links (ANR-16), owner-only — a report's owner creates a link to one persisted run; anyone with the\n * link (and the password, if set) can view that run's snapshot read-only at `/public/report/<token>`, no\n * workspace login required. The raw token is shown exactly once, right after creation. */\n\nconst DAY_MS = 86_400_000;\nconst DEFAULT_EXPIRY_DAYS = 7;\nconst MAX_EXPIRY_DAYS = 90;\n\nfunction dateInputValue(daysFromNow: number): string {\n return new Date(Date.now() + daysFromNow * DAY_MS).toISOString().slice(0, 10);\n}\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleString();\n}\n\nexport function ShareDialog({ reportId, runId }: { reportId: string; runId: string | null }) {\n const [open, setOpen] = useState(false);\n const [expiresAt, setExpiresAt] = useState(() => dateInputValue(DEFAULT_EXPIRY_DAYS));\n const [password, setPassword] = useState('');\n const [watermark, setWatermark] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [justCreated, setJustCreated] = useState<ReportShare | null>(null);\n const [copied, setCopied] = useState(false);\n\n const shares = useQuery<{ planmagnetReportShares: ReportShare[] }>(LIST_REPORT_SHARES, {\n variables: { reportId },\n skip: !open,\n });\n const [createShare, createState] = useMutation(CREATE_REPORT_SHARE, {\n refetchQueries: [{ query: LIST_REPORT_SHARES, variables: { reportId } }],\n });\n const [revokeShare] = useMutation(REVOKE_REPORT_SHARE, {\n refetchQueries: [{ query: LIST_REPORT_SHARES, variables: { reportId } }],\n });\n\n const handleCreate = async () => {\n if (!runId) return;\n setError(null);\n setJustCreated(null);\n try {\n const result = await throwOnError(\n createShare({\n variables: {\n runId,\n input: {\n expiresAt: `${expiresAt}T23:59:59.999Z`,\n password: password.trim() || undefined,\n watermark: watermark.trim() || undefined,\n },\n },\n })\n );\n const share = (result.data as { planmagnetCreateReportShare?: ReportShare } | undefined)\n ?.planmagnetCreateReportShare;\n if (share) {\n setJustCreated(share);\n setCopied(false);\n setPassword('');\n setWatermark('');\n }\n } catch (e) {\n setError(mutationErrorMessage(e, 'The share link could not be created.'));\n }\n };\n\n const handleRevoke = async (id: string) => {\n setError(null);\n try {\n await throwOnError(revokeShare({ variables: { id } }));\n if (justCreated?.id === id) setJustCreated(null);\n } catch (e) {\n setError(mutationErrorMessage(e, 'The share could not be revoked.'));\n }\n };\n\n const copyLink = async (token: string) => {\n try {\n await copyTextToClipboard(sharedReportLink(token));\n setCopied(true);\n } catch {\n setError('Could not copy — copy the link manually.');\n }\n };\n\n const list = shares.data?.planmagnetReportShares ?? [];\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) {\n setJustCreated(null);\n setError(null);\n }\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"report-share\">\n <Share2 className=\"mr-1 h-4 w-4\" />\n Share\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Share this report</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Anyone with the link can view the most recently shared run, read-only, without signing\n in.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n {!runId && (\n <p className=\"text-sm text-muted-foreground\">\n Run this report at least once before sharing it — a share links to one specific run.\n </p>\n )}\n\n {runId && (\n <div className=\"space-y-3 rounded-md border border-border p-3\">\n <div>\n <Label htmlFor=\"share-expiry\">Expires</Label>\n <Input\n id=\"share-expiry\"\n type=\"date\"\n className=\"mt-1\"\n value={expiresAt}\n min={dateInputValue(1)}\n max={dateInputValue(MAX_EXPIRY_DAYS)}\n onChange={(e) => setExpiresAt(e.target.value)}\n />\n </div>\n <div>\n <Label htmlFor=\"share-password\">Password (optional)</Label>\n <Input\n id=\"share-password\"\n type=\"password\"\n className=\"mt-1\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Leave blank for no password\"\n />\n </div>\n <div>\n <Label htmlFor=\"share-watermark\">Watermark text (optional)</Label>\n <Input\n id=\"share-watermark\"\n className=\"mt-1\"\n value={watermark}\n onChange={(e) => setWatermark(e.target.value)}\n placeholder={`Shared via PlanMagnet on ${new Date().toLocaleDateString()}`}\n />\n </div>\n <Button\n size=\"sm\"\n onClick={handleCreate}\n disabled={createState.loading}\n data-testid=\"report-share-create\"\n >\n {createState.loading ? 'Creating…' : 'Create share link'}\n </Button>\n </div>\n )}\n\n {justCreated?.token && (\n <div\n className=\"space-y-2 rounded-md border border-primary bg-primary/5 p-3\"\n data-testid=\"report-share-link\"\n >\n <p className=\"text-sm font-medium\">Link created — copy it now.</p>\n <p className=\"text-xs text-muted-foreground\">\n You will not see this link again. If you lose it, revoke this share and create a new\n one.\n </p>\n <div className=\"flex items-center gap-2\">\n <Input readOnly value={sharedReportLink(justCreated.token)} className=\"text-xs\" />\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => copyLink(justCreated.token as string)}\n >\n {copied ? <Check className=\"h-4 w-4\" /> : <Copy className=\"h-4 w-4\" />}\n {copied ? 'Copied' : 'Copy'}\n </Button>\n </div>\n </div>\n )}\n\n {error && <ErrorLine message={error} />}\n\n <div className=\"space-y-2\" data-testid=\"report-shares\">\n <h3 className=\"text-sm font-semibold\">Existing shares</h3>\n {shares.loading && !shares.data ? (\n <p className=\"text-sm text-muted-foreground\">Loading…</p>\n ) : list.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">No shares yet.</p>\n ) : (\n <ul className=\"space-y-2\">\n {list.map((share) => {\n const revoked = !!share.revokedAt;\n const expired =\n !revoked && new Date(share.expiresAt).getTime() < new Date().getTime();\n return (\n <li\n key={share.id}\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-md border border-border p-2 text-xs\"\n >\n <div className=\"space-y-0.5\">\n <div className=\"flex items-center gap-1.5\">\n <Badge variant={revoked ? 'secondary' : expired ? 'warning' : 'success'}>\n {revoked ? 'Revoked' : expired ? 'Expired' : 'Active'}\n </Badge>\n {share.hasPassword && <Badge variant=\"outline\">Password</Badge>}\n </div>\n <p className=\"text-muted-foreground\">\n Created {formatDate(share.createdAt)} · Expires{' '}\n {formatDate(share.expiresAt)}\n </p>\n <p className=\"text-muted-foreground\">\n Accessed {share.accessCount} time{share.accessCount === 1 ? '' : 's'}\n {share.lastAccessedAt ? ` · last ${formatDate(share.lastAccessedAt)}` : ''}\n </p>\n </div>\n {!revoked && (\n <ConfirmButton\n label=\"Revoke\"\n icon={null}\n title=\"Revoke this share?\"\n description=\"Anyone with this link loses access immediately. This cannot be undone.\"\n confirmLabel=\"Revoke\"\n destructive\n variant=\"outline\"\n disabled={false}\n onConfirm={() => void handleRevoke(share.id)}\n />\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Close\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* (`product/routes.ts`, `integrations/routes.ts`), this one is NOT relative to this MFE's `basePath` — the host
|
|
4
4
|
* shell mounts `SharedReportPage` on its own top-level router, OUTSIDE the authenticated app shell/basePath
|
|
5
5
|
* tree entirely (no nav chrome, no auth guard), so the path is absolute and fixed. It lives under `/public/`
|
|
6
|
-
* because fe-libs' shell-route table
|
|
7
|
-
*
|
|
6
|
+
* because fe-libs' shell-route table gives every `/public/*` path the public layout — the product header and
|
|
7
|
+
* footer, but no sidebar or breadcrumb — so an anonymous reader never sees the signed-in navigation and no host
|
|
8
|
+
* needs its own exception.
|
|
8
9
|
*/
|
|
9
10
|
export declare const SHARED_REPORT_BASE_PATH = "/public/report";
|
|
10
11
|
/** For the host's own `<Route path={SHARED_REPORT_ROUTE_PATH} element={<SharedReportPage />} />`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedReportRoute.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/reports/sharedReportRoute.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sharedReportRoute.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/reports/sharedReportRoute.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,mBAAmB,CAAC;AACxD,qGAAqG;AACrG,eAAO,MAAM,wBAAwB,0BAAsC,CAAC;AAE5E,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGtD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedReportRoute.js","names":[],"sources":["../../../src/planmagnet/reports/sharedReportRoute.ts"],"sourcesContent":["/**\n * The public shared-report route (ANR-17). Unlike every other route constant in this package\n * (`product/routes.ts`, `integrations/routes.ts`), this one is NOT relative to this MFE's `basePath` — the host\n * shell mounts `SharedReportPage` on its own top-level router, OUTSIDE the authenticated app shell/basePath\n * tree entirely (no nav chrome, no auth guard), so the path is absolute and fixed. It lives under `/public/`\n * because fe-libs' shell-route table
|
|
1
|
+
{"version":3,"file":"sharedReportRoute.js","names":[],"sources":["../../../src/planmagnet/reports/sharedReportRoute.ts"],"sourcesContent":["/**\n * The public shared-report route (ANR-17). Unlike every other route constant in this package\n * (`product/routes.ts`, `integrations/routes.ts`), this one is NOT relative to this MFE's `basePath` — the host\n * shell mounts `SharedReportPage` on its own top-level router, OUTSIDE the authenticated app shell/basePath\n * tree entirely (no nav chrome, no auth guard), so the path is absolute and fixed. It lives under `/public/`\n * because fe-libs' shell-route table gives every `/public/*` path the public layout — the product header and\n * footer, but no sidebar or breadcrumb — so an anonymous reader never sees the signed-in navigation and no host\n * needs its own exception.\n */\nexport const SHARED_REPORT_BASE_PATH = '/public/report';\n/** For the host's own `<Route path={SHARED_REPORT_ROUTE_PATH} element={<SharedReportPage />} />`. */\nexport const SHARED_REPORT_ROUTE_PATH = `${SHARED_REPORT_BASE_PATH}/:token`;\n\n/** The full, copyable link for a share token, from the current origin. */\nexport function sharedReportLink(token: string): string {\n const origin = typeof window !== 'undefined' ? window.location.origin : '';\n return `${origin}${SHARED_REPORT_BASE_PATH}/${encodeURIComponent(token)}`;\n}\n"],"mappings":";AASA,IAAa,IAA0B,kBAE1B,IAA2B,GAAG,EAAwB;AAGnE,SAAgB,EAAiB,GAAuB;AAEtD,QAAO,GADQ,OAAO,SAAW,MAAc,OAAO,SAAS,SAAS,KACrD,EAAwB,GAAG,mBAAmB,EAAM"}
|