@burdenoff/microfe-export 2026.625.2 → 2026.626.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.
- package/dist/export/pages/CreateExportPage.js +95 -90
- package/dist/export/pages/CreateExportPage.js.map +1 -1
- package/dist/export/pages/ExportDashboardPage.js +79 -46
- package/dist/export/pages/ExportDashboardPage.js.map +1 -1
- package/dist/export/pages/ExportDetailPage.js +157 -134
- package/dist/export/pages/ExportDetailPage.js.map +1 -1
- package/dist/export/pages/ExportHistoryPage.js +139 -135
- package/dist/export/pages/ExportHistoryPage.js.map +1 -1
- package/dist/export/pages/ExportTemplatesPage.js +172 -168
- package/dist/export/pages/ExportTemplatesPage.js.map +1 -1
- package/dist/export/pages/TemplateDetailPage.js +103 -97
- package/dist/export/pages/TemplateDetailPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { useExport as e } from "../providers/ExportProvider.js";
|
|
2
|
-
import { useCancelExportJob as t, useDeleteExportJob as n, useExportJob as r, useExportJobProgress as
|
|
3
|
-
import { cn as
|
|
4
|
-
import { getArchiveFormatDisplayName as
|
|
5
|
-
import { ExportStatusBadge as
|
|
6
|
-
import { ExportProgressBar as
|
|
7
|
-
import { PageLayout as
|
|
8
|
-
import { nativeImpact as
|
|
9
|
-
import { useState as
|
|
10
|
-
import { AlertCircle as
|
|
11
|
-
import { useNavigate as
|
|
12
|
-
import { jsx as
|
|
13
|
-
import { CTAOverflowMenu as
|
|
2
|
+
import { useCancelExportJob as t, useDeleteExportJob as n, useExportJob as r, useExportJobProgress as ee, useRetryExportJob as te } from "../hooks/useExportQueries.js";
|
|
3
|
+
import { cn as i } from "../utils/cn.js";
|
|
4
|
+
import { getArchiveFormatDisplayName as ne, getFormatDisplayName as re } from "../constants/enums.js";
|
|
5
|
+
import { ExportStatusBadge as a } from "../components/ExportStatusBadge.js";
|
|
6
|
+
import { ExportProgressBar as o } from "../components/ExportProgressBar.js";
|
|
7
|
+
import { PageLayout as s, PageSection as c } from "../components/PageLayout.js";
|
|
8
|
+
import { nativeImpact as l, nativeNotify as u } from "../../utils/nativeBridge.js";
|
|
9
|
+
import { useState as d } from "react";
|
|
10
|
+
import { AlertCircle as f, ArrowLeft as p, CheckCircle2 as m, Clock as h, Download as g, FileDown as ie, FileText as ae, HardDrive as _, Loader2 as v, RotateCcw as y, Trash2 as b, XCircle as x } from "lucide-react";
|
|
11
|
+
import { useNavigate as S, useParams as oe } from "react-router-dom";
|
|
12
|
+
import { jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
13
|
+
import { CTAOverflowMenu as T, GlassCard as E, IllustratedEmptyState as D, NextSteps as O } from "@burdenoff/fe-libs/ui";
|
|
14
14
|
//#region src/export/pages/ExportDetailPage.tsx
|
|
15
15
|
function k(e) {
|
|
16
16
|
if (e === 0) return "0 B";
|
|
@@ -25,150 +25,168 @@ function k(e) {
|
|
|
25
25
|
}
|
|
26
26
|
var A = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
|
|
27
27
|
function j() {
|
|
28
|
-
let { jobId: j } =
|
|
28
|
+
let { jobId: j } = oe(), { basePath: M = "/", navigate: N } = e(), P = S(), F = () => {
|
|
29
29
|
window.history.length > 1 ? P(-1) : N?.(M);
|
|
30
|
-
}, { data: I, isLoading: L } = r(j), { data: R } =
|
|
30
|
+
}, { data: I, isLoading: L } = r(j), { data: R } = ee(j), [z, B] = d(null), { cancelJob: se, isPending: V } = t(), { deleteJob: H, isPending: U } = n(), { retryJob: W, isPending: G } = te(), K = R ?? I ?? null, q = K?.status === "CREATED" || K?.status === "QUEUED" || K?.status === "RUNNING", J = K?.status === "COMPLETED", ce = K?.status === "FAILED", Y = K?.status === "FAILED" || K?.status === "CANCELLED" || K?.status === "EXPIRED", le = async () => {
|
|
31
31
|
j && B("cancel");
|
|
32
|
-
},
|
|
33
|
-
j && (
|
|
34
|
-
},
|
|
32
|
+
}, ue = async () => {
|
|
33
|
+
j && (l("medium"), B("delete"));
|
|
34
|
+
}, de = async () => {
|
|
35
35
|
if (!(!j || !z)) {
|
|
36
36
|
try {
|
|
37
|
-
z === "cancel" ? await
|
|
37
|
+
z === "cancel" ? await se(j) : (await H(j), N?.(M)), u("success");
|
|
38
38
|
} catch {
|
|
39
|
-
|
|
39
|
+
u("error");
|
|
40
40
|
}
|
|
41
41
|
B(null);
|
|
42
42
|
}
|
|
43
43
|
}, X = async () => {
|
|
44
44
|
if (!j) return;
|
|
45
|
-
let e = await
|
|
45
|
+
let e = await W(j);
|
|
46
46
|
e?.id && N?.(A(M, e.id));
|
|
47
|
-
}, Z =
|
|
48
|
-
if (L) return /* @__PURE__ */
|
|
47
|
+
}, Z = K?.parts.find((e) => e.downloadUrl);
|
|
48
|
+
if (L) return /* @__PURE__ */ w("div", {
|
|
49
49
|
className: "flex flex-col items-center justify-center py-20",
|
|
50
|
-
children: [/* @__PURE__ */
|
|
50
|
+
children: [/* @__PURE__ */ C(v, {
|
|
51
51
|
size: 48,
|
|
52
52
|
className: "animate-spin text-primary"
|
|
53
|
-
}), /* @__PURE__ */
|
|
53
|
+
}), /* @__PURE__ */ C("span", {
|
|
54
54
|
className: "mt-6 text-sm font-medium text-text-secondary",
|
|
55
55
|
children: "Loading export details…"
|
|
56
56
|
})]
|
|
57
57
|
});
|
|
58
|
-
if (!
|
|
58
|
+
if (!K) return /* @__PURE__ */ w(s, {
|
|
59
59
|
showHeader: !1,
|
|
60
|
-
children: [/* @__PURE__ */
|
|
60
|
+
children: [/* @__PURE__ */ w("div", {
|
|
61
61
|
className: "flex items-center gap-3 mb-6",
|
|
62
|
-
children: [/* @__PURE__ */
|
|
62
|
+
children: [/* @__PURE__ */ C("button", {
|
|
63
63
|
type: "button",
|
|
64
64
|
onClick: F,
|
|
65
|
+
"aria-label": "Go back",
|
|
65
66
|
className: "p-2.5 hover:bg-accent rounded-lg transition-all duration-200",
|
|
66
|
-
children: /* @__PURE__ */
|
|
67
|
-
}), /* @__PURE__ */
|
|
67
|
+
children: /* @__PURE__ */ C(p, { size: 20 })
|
|
68
|
+
}), /* @__PURE__ */ C("h1", {
|
|
68
69
|
className: "text-xl font-bold",
|
|
69
70
|
children: "Export Not Found"
|
|
70
71
|
})]
|
|
71
|
-
}), /* @__PURE__ */
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}),
|
|
82
|
-
/* @__PURE__ */ T("p", {
|
|
83
|
-
className: "text-status-error-text mb-6",
|
|
84
|
-
children: "The export job you are looking for does not exist or has been deleted."
|
|
85
|
-
}),
|
|
86
|
-
/* @__PURE__ */ E("button", {
|
|
87
|
-
type: "button",
|
|
88
|
-
onClick: F,
|
|
89
|
-
className: "inline-flex items-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium",
|
|
90
|
-
children: [/* @__PURE__ */ T(h, { size: 18 }), "Back to Dashboard"]
|
|
91
|
-
})
|
|
92
|
-
]
|
|
72
|
+
}), /* @__PURE__ */ C(D, {
|
|
73
|
+
illustration: "empty-data",
|
|
74
|
+
title: "Export not found",
|
|
75
|
+
description: "The export job you are looking for does not exist or has been deleted.",
|
|
76
|
+
action: /* @__PURE__ */ w("button", {
|
|
77
|
+
type: "button",
|
|
78
|
+
onClick: F,
|
|
79
|
+
className: "inline-flex items-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium",
|
|
80
|
+
children: [/* @__PURE__ */ C(p, { size: 18 }), "Back to Dashboard"]
|
|
81
|
+
})
|
|
93
82
|
})]
|
|
94
83
|
});
|
|
95
84
|
let Q = Z ? {
|
|
96
85
|
label: "Download",
|
|
97
|
-
icon: /* @__PURE__ */
|
|
86
|
+
icon: /* @__PURE__ */ C(g, { size: 16 }),
|
|
98
87
|
onSelect: () => {
|
|
99
88
|
Z.downloadUrl && window.location.assign(Z.downloadUrl);
|
|
100
89
|
}
|
|
101
90
|
} : Y ? {
|
|
102
91
|
label: "Retry Export",
|
|
103
|
-
icon: /* @__PURE__ */
|
|
104
|
-
disabled:
|
|
92
|
+
icon: /* @__PURE__ */ C(y, { size: 16 }),
|
|
93
|
+
disabled: G,
|
|
105
94
|
onSelect: () => {
|
|
106
95
|
X();
|
|
107
96
|
}
|
|
108
97
|
} : void 0, $ = [];
|
|
109
|
-
|
|
98
|
+
Y && Z && $.push({
|
|
110
99
|
label: "Retry Export",
|
|
111
|
-
icon: /* @__PURE__ */
|
|
112
|
-
disabled:
|
|
100
|
+
icon: /* @__PURE__ */ C(y, { size: 16 }),
|
|
101
|
+
disabled: G,
|
|
113
102
|
onSelect: () => {
|
|
114
103
|
X();
|
|
115
104
|
}
|
|
116
|
-
}),
|
|
105
|
+
}), q && $.push({
|
|
117
106
|
label: "Cancel Export",
|
|
118
|
-
icon: /* @__PURE__ */
|
|
119
|
-
disabled:
|
|
107
|
+
icon: /* @__PURE__ */ C(x, { size: 16 }),
|
|
108
|
+
disabled: V,
|
|
120
109
|
onSelect: () => {
|
|
121
|
-
|
|
110
|
+
le();
|
|
122
111
|
}
|
|
123
|
-
}),
|
|
112
|
+
}), q || $.push({
|
|
124
113
|
label: "Delete Export",
|
|
125
|
-
icon: /* @__PURE__ */
|
|
114
|
+
icon: /* @__PURE__ */ C(b, { size: 16 }),
|
|
126
115
|
intent: "destructive",
|
|
127
|
-
disabled:
|
|
116
|
+
disabled: U,
|
|
128
117
|
onSelect: () => {
|
|
129
|
-
|
|
118
|
+
ue();
|
|
130
119
|
}
|
|
131
|
-
})
|
|
120
|
+
});
|
|
121
|
+
let fe = [
|
|
122
|
+
{
|
|
123
|
+
id: "download",
|
|
124
|
+
label: "Download your export",
|
|
125
|
+
description: "Grab the generated files below before their download links expire.",
|
|
126
|
+
icon: /* @__PURE__ */ C(g, { size: 16 }),
|
|
127
|
+
onClick: () => {
|
|
128
|
+
Z?.downloadUrl && window.location.assign(Z.downloadUrl);
|
|
129
|
+
},
|
|
130
|
+
done: J
|
|
131
|
+
},
|
|
132
|
+
...Y ? [{
|
|
133
|
+
id: "retry",
|
|
134
|
+
label: "Retry this export",
|
|
135
|
+
description: "This export didn't finish — start a fresh run with the same settings.",
|
|
136
|
+
icon: /* @__PURE__ */ C(y, { size: 16 }),
|
|
137
|
+
onClick: () => {
|
|
138
|
+
X();
|
|
139
|
+
}
|
|
140
|
+
}] : [],
|
|
141
|
+
{
|
|
142
|
+
id: "history",
|
|
143
|
+
label: "Back to History",
|
|
144
|
+
description: "See all your exports and track other jobs in progress.",
|
|
145
|
+
icon: /* @__PURE__ */ C(h, { size: 16 }),
|
|
146
|
+
onClick: () => N?.(M)
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
return /* @__PURE__ */ w(s, {
|
|
132
150
|
showHeader: !1,
|
|
133
151
|
children: [
|
|
134
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ C("div", {
|
|
135
153
|
className: "relative overflow-hidden rounded-xl bg-accent-wash border border-border-seam p-6 shadow-[var(--shadow-pop)]",
|
|
136
|
-
children: /* @__PURE__ */
|
|
154
|
+
children: /* @__PURE__ */ w("div", {
|
|
137
155
|
className: "relative z-10 flex flex-col sm:flex-row sm:items-center gap-4",
|
|
138
156
|
children: [
|
|
139
|
-
/* @__PURE__ */
|
|
157
|
+
/* @__PURE__ */ C("button", {
|
|
140
158
|
type: "button",
|
|
141
159
|
onClick: F,
|
|
142
160
|
"aria-label": "Go back",
|
|
143
161
|
className: "p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 self-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
|
|
144
|
-
children: /* @__PURE__ */
|
|
162
|
+
children: /* @__PURE__ */ C(p, { size: 20 })
|
|
145
163
|
}),
|
|
146
|
-
/* @__PURE__ */
|
|
164
|
+
/* @__PURE__ */ w("div", {
|
|
147
165
|
className: "flex items-center gap-3 flex-1 min-w-0",
|
|
148
166
|
children: [
|
|
149
|
-
/* @__PURE__ */
|
|
167
|
+
/* @__PURE__ */ C("div", {
|
|
150
168
|
className: "p-2.5 rounded-lg bg-action-primary-bg/10",
|
|
151
|
-
children: /* @__PURE__ */
|
|
169
|
+
children: /* @__PURE__ */ C(g, {
|
|
152
170
|
size: 24,
|
|
153
171
|
className: "text-primary"
|
|
154
172
|
})
|
|
155
173
|
}),
|
|
156
|
-
/* @__PURE__ */
|
|
174
|
+
/* @__PURE__ */ w("div", {
|
|
157
175
|
className: "flex-1 min-w-0",
|
|
158
|
-
children: [/* @__PURE__ */
|
|
176
|
+
children: [/* @__PURE__ */ C("h1", {
|
|
159
177
|
className: "text-xl sm:text-2xl font-bold truncate",
|
|
160
|
-
children:
|
|
161
|
-
}), /* @__PURE__ */
|
|
178
|
+
children: K.name
|
|
179
|
+
}), /* @__PURE__ */ C("p", {
|
|
162
180
|
className: "text-sm text-text-secondary mt-1",
|
|
163
|
-
children:
|
|
181
|
+
children: K.entityTypes.join(", ")
|
|
164
182
|
})]
|
|
165
183
|
}),
|
|
166
|
-
/* @__PURE__ */
|
|
184
|
+
/* @__PURE__ */ C(a, { status: K.status })
|
|
167
185
|
]
|
|
168
186
|
}),
|
|
169
|
-
(Q || $.length > 0) && /* @__PURE__ */
|
|
187
|
+
(Q || $.length > 0) && /* @__PURE__ */ C("div", {
|
|
170
188
|
className: "flex-shrink-0",
|
|
171
|
-
children: /* @__PURE__ */ T
|
|
189
|
+
children: /* @__PURE__ */ C(T, {
|
|
172
190
|
primary: Q,
|
|
173
191
|
actions: $
|
|
174
192
|
})
|
|
@@ -176,91 +194,96 @@ function j() {
|
|
|
176
194
|
]
|
|
177
195
|
})
|
|
178
196
|
}),
|
|
179
|
-
(
|
|
197
|
+
(K.status === "RUNNING" || K.status === "QUEUED") && /* @__PURE__ */ w("div", {
|
|
180
198
|
className: "border border-border-seam rounded-xl bg-status-info-bg-subtle p-6",
|
|
181
|
-
children: [/* @__PURE__ */
|
|
182
|
-
progress:
|
|
199
|
+
children: [/* @__PURE__ */ C(o, {
|
|
200
|
+
progress: K.progress,
|
|
183
201
|
size: "lg"
|
|
184
|
-
}),
|
|
202
|
+
}), K.processedEntities != null && K.totalEntities != null && /* @__PURE__ */ w("p", {
|
|
185
203
|
className: "text-sm text-text-secondary mt-2",
|
|
186
204
|
children: [
|
|
187
|
-
|
|
205
|
+
K.processedEntities.toLocaleString(),
|
|
188
206
|
" /",
|
|
189
207
|
" ",
|
|
190
|
-
|
|
208
|
+
K.totalEntities.toLocaleString(),
|
|
191
209
|
" entities processed"
|
|
192
210
|
]
|
|
193
211
|
})]
|
|
194
212
|
}),
|
|
195
|
-
|
|
213
|
+
ce && K.errorMessage && /* @__PURE__ */ w("div", {
|
|
196
214
|
className: "border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4",
|
|
197
|
-
children: [/* @__PURE__ */
|
|
215
|
+
children: [/* @__PURE__ */ C(f, {
|
|
198
216
|
size: 24,
|
|
199
217
|
className: "text-status-error-text flex-shrink-0 mt-0.5"
|
|
200
|
-
}), /* @__PURE__ */
|
|
218
|
+
}), /* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("h3", {
|
|
201
219
|
className: "font-semibold text-status-error-text mb-1",
|
|
202
220
|
children: "Export Failed"
|
|
203
|
-
}), /* @__PURE__ */
|
|
221
|
+
}), /* @__PURE__ */ C("p", {
|
|
204
222
|
className: "text-sm text-status-error-text",
|
|
205
|
-
children:
|
|
223
|
+
children: K.errorMessage
|
|
206
224
|
})] })]
|
|
207
225
|
}),
|
|
208
|
-
/* @__PURE__ */
|
|
226
|
+
/* @__PURE__ */ C("div", {
|
|
209
227
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4",
|
|
210
228
|
children: [
|
|
211
229
|
{
|
|
212
|
-
icon: /* @__PURE__ */
|
|
230
|
+
icon: /* @__PURE__ */ C(ae, { size: 18 }),
|
|
213
231
|
label: "Format",
|
|
214
|
-
value:
|
|
232
|
+
value: re(K.format)
|
|
215
233
|
},
|
|
216
234
|
{
|
|
217
|
-
icon: /* @__PURE__ */
|
|
235
|
+
icon: /* @__PURE__ */ C(_, { size: 18 }),
|
|
218
236
|
label: "Archive",
|
|
219
|
-
value:
|
|
237
|
+
value: ne(K.archiveFormat)
|
|
220
238
|
},
|
|
221
239
|
{
|
|
222
|
-
icon: /* @__PURE__ */
|
|
240
|
+
icon: /* @__PURE__ */ C(g, { size: 18 }),
|
|
223
241
|
label: "Total Size",
|
|
224
|
-
value:
|
|
242
|
+
value: K.totalSizeBytes == null ? "-" : k(K.totalSizeBytes)
|
|
225
243
|
},
|
|
226
244
|
{
|
|
227
|
-
icon: /* @__PURE__ */
|
|
245
|
+
icon: /* @__PURE__ */ C(h, { size: 18 }),
|
|
228
246
|
label: "Created",
|
|
229
|
-
value: new Date(
|
|
247
|
+
value: new Date(K.createdAt).toLocaleDateString()
|
|
230
248
|
}
|
|
231
|
-
].map((e) => /* @__PURE__ */ E
|
|
249
|
+
].map((e) => /* @__PURE__ */ w(E, {
|
|
232
250
|
treatment: "metric",
|
|
233
251
|
className: "p-4 border-border-seam",
|
|
234
|
-
children: [/* @__PURE__ */
|
|
252
|
+
children: [/* @__PURE__ */ w("div", {
|
|
235
253
|
className: "flex items-center gap-2 text-text-secondary mb-1",
|
|
236
|
-
children: [e.icon, /* @__PURE__ */
|
|
254
|
+
children: [e.icon, /* @__PURE__ */ C("span", {
|
|
237
255
|
className: "text-xs font-medium",
|
|
238
256
|
children: e.label
|
|
239
257
|
})]
|
|
240
|
-
}), /* @__PURE__ */
|
|
258
|
+
}), /* @__PURE__ */ C("p", {
|
|
241
259
|
className: "font-bold text-lg tabular-nums",
|
|
242
260
|
children: e.value
|
|
243
261
|
})]
|
|
244
262
|
}, e.label))
|
|
245
263
|
}),
|
|
246
|
-
|
|
264
|
+
/* @__PURE__ */ C(O, {
|
|
265
|
+
storageKey: `export-detail-${K.id}`,
|
|
266
|
+
title: "What's next",
|
|
267
|
+
steps: fe
|
|
268
|
+
}),
|
|
269
|
+
K.parts.length > 0 && /* @__PURE__ */ C(c, {
|
|
247
270
|
title: "Export Parts",
|
|
248
|
-
description: `${
|
|
249
|
-
children: /* @__PURE__ */
|
|
271
|
+
description: `${K.parts.length} file(s) generated`,
|
|
272
|
+
children: /* @__PURE__ */ C("div", {
|
|
250
273
|
className: "space-y-2",
|
|
251
|
-
children:
|
|
252
|
-
className:
|
|
253
|
-
children: [/* @__PURE__ */
|
|
274
|
+
children: K.parts.map((e) => /* @__PURE__ */ w("div", {
|
|
275
|
+
className: i("border border-border-seam rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 bg-bg-surface", "hover:border-primary/40 transition-colors duration-200"),
|
|
276
|
+
children: [/* @__PURE__ */ w("div", {
|
|
254
277
|
className: "flex items-center gap-3 min-w-0",
|
|
255
|
-
children: [/* @__PURE__ */
|
|
278
|
+
children: [/* @__PURE__ */ C(ie, {
|
|
256
279
|
size: 20,
|
|
257
280
|
className: "text-primary flex-shrink-0"
|
|
258
|
-
}), /* @__PURE__ */
|
|
281
|
+
}), /* @__PURE__ */ w("div", {
|
|
259
282
|
className: "min-w-0",
|
|
260
|
-
children: [/* @__PURE__ */
|
|
283
|
+
children: [/* @__PURE__ */ C("p", {
|
|
261
284
|
className: "font-medium truncate",
|
|
262
285
|
children: e.fileName
|
|
263
|
-
}), /* @__PURE__ */
|
|
286
|
+
}), /* @__PURE__ */ w("p", {
|
|
264
287
|
className: "text-sm text-text-secondary",
|
|
265
288
|
children: [
|
|
266
289
|
e.entityTypes.join(", "),
|
|
@@ -271,67 +294,67 @@ function j() {
|
|
|
271
294
|
]
|
|
272
295
|
})]
|
|
273
296
|
})]
|
|
274
|
-
}), /* @__PURE__ */
|
|
297
|
+
}), /* @__PURE__ */ C("div", {
|
|
275
298
|
className: "flex items-center gap-2 flex-shrink-0",
|
|
276
|
-
children: e.downloadUrl ? /* @__PURE__ */
|
|
299
|
+
children: e.downloadUrl ? /* @__PURE__ */ w("a", {
|
|
277
300
|
href: e.downloadUrl,
|
|
278
301
|
className: "inline-flex items-center gap-2 px-3 py-1.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors text-sm font-medium",
|
|
279
|
-
children: [/* @__PURE__ */
|
|
280
|
-
}) : /* @__PURE__ */
|
|
302
|
+
children: [/* @__PURE__ */ C(g, { size: 14 }), "Download"]
|
|
303
|
+
}) : /* @__PURE__ */ w("span", {
|
|
281
304
|
className: "inline-flex items-center gap-1.5 text-sm text-text-secondary",
|
|
282
|
-
children: [/* @__PURE__ */
|
|
305
|
+
children: [/* @__PURE__ */ C(h, { size: 14 }), "Pending"]
|
|
283
306
|
})
|
|
284
307
|
})]
|
|
285
308
|
}, e.id))
|
|
286
309
|
})
|
|
287
310
|
}),
|
|
288
|
-
|
|
311
|
+
J && K.parts.length === 0 && /* @__PURE__ */ w("div", {
|
|
289
312
|
className: "border-2 border-border-default rounded-xl bg-status-success-bg-subtle p-6 text-center",
|
|
290
313
|
children: [
|
|
291
|
-
/* @__PURE__ */
|
|
314
|
+
/* @__PURE__ */ C(m, {
|
|
292
315
|
size: 48,
|
|
293
316
|
className: "text-status-success-text mx-auto mb-3"
|
|
294
317
|
}),
|
|
295
|
-
/* @__PURE__ */
|
|
318
|
+
/* @__PURE__ */ C("h3", {
|
|
296
319
|
className: "text-lg font-bold text-status-success-text mb-1",
|
|
297
320
|
children: "Export Complete"
|
|
298
321
|
}),
|
|
299
|
-
/* @__PURE__ */
|
|
322
|
+
/* @__PURE__ */ C("p", {
|
|
300
323
|
className: "text-sm text-status-success-text",
|
|
301
324
|
children: "Your export has completed but no download parts are available yet."
|
|
302
325
|
})
|
|
303
326
|
]
|
|
304
327
|
}),
|
|
305
|
-
z && /* @__PURE__ */
|
|
328
|
+
z && /* @__PURE__ */ C("div", {
|
|
306
329
|
role: "dialog",
|
|
307
330
|
"aria-modal": "true",
|
|
308
331
|
"aria-labelledby": "export-confirm-title",
|
|
309
332
|
className: "fixed inset-0 z-50 flex items-center justify-center bg-bg-surface/80 backdrop-blur-sm p-4",
|
|
310
|
-
children: /* @__PURE__ */
|
|
333
|
+
children: /* @__PURE__ */ w("div", {
|
|
311
334
|
className: "w-full max-w-md rounded-xl border-2 border-border-subtle bg-bg-surface p-6 shadow-xl",
|
|
312
335
|
children: [
|
|
313
|
-
/* @__PURE__ */
|
|
336
|
+
/* @__PURE__ */ C("h2", {
|
|
314
337
|
id: "export-confirm-title",
|
|
315
338
|
className: "text-lg font-bold",
|
|
316
339
|
children: z === "cancel" ? "Cancel export?" : "Delete export?"
|
|
317
340
|
}),
|
|
318
|
-
/* @__PURE__ */
|
|
341
|
+
/* @__PURE__ */ C("p", {
|
|
319
342
|
className: "mt-2 text-sm text-text-secondary",
|
|
320
343
|
children: z === "cancel" ? "This stops the running export job. You can retry it later if needed." : "This removes the export job from history. This action cannot be undone."
|
|
321
344
|
}),
|
|
322
|
-
/* @__PURE__ */
|
|
345
|
+
/* @__PURE__ */ w("div", {
|
|
323
346
|
className: "mt-6 flex flex-col-reverse sm:flex-row sm:justify-end gap-3",
|
|
324
|
-
children: [/* @__PURE__ */
|
|
347
|
+
children: [/* @__PURE__ */ C("button", {
|
|
325
348
|
type: "button",
|
|
326
349
|
onClick: () => B(null),
|
|
327
350
|
className: "px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium",
|
|
328
351
|
children: "Keep Export"
|
|
329
|
-
}), /* @__PURE__ */
|
|
352
|
+
}), /* @__PURE__ */ w("button", {
|
|
330
353
|
type: "button",
|
|
331
|
-
onClick:
|
|
332
|
-
disabled:
|
|
354
|
+
onClick: de,
|
|
355
|
+
disabled: V || U,
|
|
333
356
|
className: "inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium disabled:opacity-50",
|
|
334
|
-
children: [
|
|
357
|
+
children: [V || U ? /* @__PURE__ */ C(v, {
|
|
335
358
|
size: 16,
|
|
336
359
|
className: "animate-spin"
|
|
337
360
|
}) : null, z === "cancel" ? "Cancel Export" : "Delete Export"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router-dom\";\nimport {\n ArrowLeft,\n Download,\n XCircle,\n Trash2,\n RotateCcw,\n Loader2,\n FileDown,\n Clock,\n AlertCircle,\n CheckCircle2,\n HardDrive,\n FileText,\n} from \"lucide-react\";\nimport { GlassCard, CTAOverflowMenu } from \"@burdenoff/fe-libs/ui\";\nimport type { CTAAction, CTAPrimaryAction } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useExportJob,\n useExportJobProgress,\n useCancelExportJob,\n useDeleteExportJob,\n useRetryExportJob,\n} from \"../hooks/useExportQueries\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { ExportProgressBar } from \"../components/ExportProgressBar\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n getFormatDisplayName,\n getArchiveFormatDisplayName,\n} from \"../constants/enums\";\nimport { cn } from \"../utils/cn\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/**\n * Format bytes to human-readable size\n */\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\n}\n\n/**\n * Helper to join paths correctly\n */\nconst joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n return cleanBase + cleanPath;\n};\n\n/**\n * Export Detail Page\n * Single export job detail view with parts, progress, download links.\n * Uses GraphQL subscriptions for live progress updates when a job is active.\n */\nexport function ExportDetailPage() {\n const { jobId } = useParams<{ jobId: string }>();\n const { basePath = \"/\", navigate } = useExport();\n const routerNavigate = useNavigate();\n\n const handleBack = () => {\n if (window.history.length > 1) {\n routerNavigate(-1);\n } else {\n navigate?.(basePath);\n }\n };\n\n const { data: job, isLoading } = useExportJob(jobId);\n const { data: progressJob } = useExportJobProgress(jobId);\n const [pendingAction, setPendingAction] = useState<\n \"cancel\" | \"delete\" | null\n >(null);\n\n const { cancelJob, isPending: cancelling } = useCancelExportJob();\n const { deleteJob, isPending: deleting } = useDeleteExportJob();\n const { retryJob, isPending: retrying } = useRetryExportJob();\n\n const liveJob = progressJob ?? job ?? null;\n\n // Jobs in CREATED/QUEUED/RUNNING can still be cancelled\n const isCancellable =\n liveJob?.status === \"CREATED\" ||\n liveJob?.status === \"QUEUED\" ||\n liveJob?.status === \"RUNNING\";\n const isCompleted = liveJob?.status === \"COMPLETED\";\n const isFailed = liveJob?.status === \"FAILED\";\n const isRetryable =\n liveJob?.status === \"FAILED\" ||\n liveJob?.status === \"CANCELLED\" ||\n liveJob?.status === \"EXPIRED\";\n\n const handleCancel = async () => {\n if (!jobId) return;\n setPendingAction(\"cancel\");\n };\n\n const handleDelete = async () => {\n if (!jobId) return;\n nativeImpact(\"medium\");\n setPendingAction(\"delete\");\n };\n\n const confirmPendingAction = async () => {\n if (!jobId || !pendingAction) return;\n try {\n if (pendingAction === \"cancel\") {\n await cancelJob(jobId);\n } else {\n await deleteJob(jobId);\n navigate?.(basePath);\n }\n nativeNotify(\"success\");\n } catch {\n nativeNotify(\"error\");\n }\n setPendingAction(null);\n };\n\n const handleRetry = async () => {\n if (!jobId) return;\n const newJob = await retryJob(jobId);\n // Navigate to the new job created by retry\n if (newJob?.id) {\n navigate?.(joinPath(basePath, newJob.id));\n }\n };\n\n // Build a single primary CTA + overflow menu for the header. Download (the\n // first available part) is the primary action when the export is ready;\n // otherwise the most relevant lifecycle action leads. Destructive Delete\n // always lives in the overflow menu and is confirmed via the dialog.\n const firstDownloadablePart = liveJob?.parts.find((p) => p.downloadUrl);\n\n // Loading\n if (isLoading) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20\">\n <Loader2 size={48} className=\"animate-spin text-primary\" />\n <span className=\"mt-6 text-sm font-medium text-text-secondary\">\n Loading export details…\n </span>\n </div>\n );\n }\n\n // Not found\n if (!liveJob) {\n return (\n <PageLayout showHeader={false}>\n <div className=\"flex items-center gap-3 mb-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <h1 className=\"text-xl font-bold\">Export Not Found</h1>\n </div>\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-12 text-center\">\n <AlertCircle\n size={48}\n className=\"text-status-error-text mx-auto mb-4\"\n />\n <h3 className=\"text-lg font-bold text-status-error-text mb-2\">\n Export Not Found\n </h3>\n <p className=\"text-status-error-text mb-6\">\n The export job you are looking for does not exist or has been\n deleted.\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <ArrowLeft size={18} />\n Back to Dashboard\n </button>\n </div>\n </PageLayout>\n );\n }\n\n // Primary CTA: download when ready, otherwise retry when retryable.\n const primaryAction: CTAPrimaryAction | undefined = firstDownloadablePart\n ? {\n label: \"Download\",\n icon: <Download size={16} />,\n onSelect: () => {\n if (firstDownloadablePart.downloadUrl) {\n window.location.assign(firstDownloadablePart.downloadUrl);\n }\n },\n }\n : isRetryable\n ? {\n label: \"Retry Export\",\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n }\n : undefined;\n\n // Overflow actions: lifecycle controls + destructive delete.\n const overflowActions: CTAAction[] = [];\n if (isRetryable && firstDownloadablePart) {\n overflowActions.push({\n label: \"Retry Export\",\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n });\n }\n if (isCancellable) {\n overflowActions.push({\n label: \"Cancel Export\",\n icon: <XCircle size={16} />,\n disabled: cancelling,\n onSelect: () => {\n void handleCancel();\n },\n });\n }\n if (!isCancellable) {\n overflowActions.push({\n label: \"Delete Export\",\n icon: <Trash2 size={16} />,\n intent: \"destructive\",\n disabled: deleting,\n onSelect: () => {\n void handleDelete();\n },\n });\n }\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-accent-wash border border-border-seam p-6 shadow-[var(--shadow-pop)]\">\n <div className=\"relative z-10 flex flex-col sm:flex-row sm:items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label=\"Go back\"\n className=\"p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 self-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3 flex-1 min-w-0\">\n <div className=\"p-2.5 rounded-lg bg-action-primary-bg/10\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-xl sm:text-2xl font-bold truncate\">\n {liveJob.name}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {liveJob.entityTypes.join(\", \")}\n </p>\n </div>\n <ExportStatusBadge status={liveJob.status} />\n </div>\n {(primaryAction || overflowActions.length > 0) && (\n <div className=\"flex-shrink-0\">\n <CTAOverflowMenu\n primary={primaryAction}\n actions={overflowActions}\n />\n </div>\n )}\n </div>\n </div>\n\n {/* Progress (for active jobs) */}\n {(liveJob.status === \"RUNNING\" || liveJob.status === \"QUEUED\") && (\n <div className=\"border border-border-seam rounded-xl bg-status-info-bg-subtle p-6\">\n <ExportProgressBar progress={liveJob.progress} size=\"lg\" />\n {liveJob.processedEntities != null &&\n liveJob.totalEntities != null && (\n <p className=\"text-sm text-text-secondary mt-2\">\n {liveJob.processedEntities.toLocaleString()} /{\" \"}\n {liveJob.totalEntities.toLocaleString()} entities processed\n </p>\n )}\n </div>\n )}\n\n {/* Error (for failed jobs) */}\n {isFailed && liveJob.errorMessage && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4\">\n <AlertCircle\n size={24}\n className=\"text-status-error-text flex-shrink-0 mt-0.5\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n Export Failed\n </h3>\n <p className=\"text-sm text-status-error-text\">\n {liveJob.errorMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Info Grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {[\n {\n icon: <FileText size={18} />,\n label: \"Format\",\n value: getFormatDisplayName(liveJob.format),\n },\n {\n icon: <HardDrive size={18} />,\n label: \"Archive\",\n value: getArchiveFormatDisplayName(liveJob.archiveFormat),\n },\n {\n icon: <Download size={18} />,\n label: \"Total Size\",\n value:\n liveJob.totalSizeBytes != null\n ? formatBytes(liveJob.totalSizeBytes)\n : \"-\",\n },\n {\n icon: <Clock size={18} />,\n label: \"Created\",\n value: new Date(liveJob.createdAt).toLocaleDateString(),\n },\n ].map((item) => (\n <GlassCard\n key={item.label}\n treatment=\"metric\"\n className=\"p-4 border-border-seam\"\n >\n <div className=\"flex items-center gap-2 text-text-secondary mb-1\">\n {item.icon}\n <span className=\"text-xs font-medium\">{item.label}</span>\n </div>\n <p className=\"font-bold text-lg tabular-nums\">{item.value}</p>\n </GlassCard>\n ))}\n </div>\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title=\"Export Parts\"\n description={`${liveJob.parts.length} file(s) generated`}\n >\n <div className=\"space-y-2\">\n {liveJob.parts.map((part) => (\n <div\n key={part.id}\n className={cn(\n \"border border-border-seam rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n )}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileDown size={20} className=\"text-primary flex-shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"font-medium truncate\">{part.fileName}</p>\n <p className=\"text-sm text-text-secondary\">\n {part.entityTypes.join(\", \")} ·{\" \"}\n {formatBytes(part.sizeBytes)}\n {part.downloadExpiresAt &&\n ` · expires ${new Date(part.downloadExpiresAt).toLocaleDateString()}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {part.downloadUrl ? (\n <a\n href={part.downloadUrl}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors text-sm font-medium\"\n >\n <Download size={14} />\n Download\n </a>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock size={14} />\n Pending\n </span>\n )}\n </div>\n </div>\n ))}\n </div>\n </PageSection>\n )}\n\n {/* Completed message when no parts yet */}\n {isCompleted && liveJob.parts.length === 0 && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-success-bg-subtle p-6 text-center\">\n <CheckCircle2\n size={48}\n className=\"text-status-success-text mx-auto mb-3\"\n />\n <h3 className=\"text-lg font-bold text-status-success-text mb-1\">\n Export Complete\n </h3>\n <p className=\"text-sm text-status-success-text\">\n Your export has completed but no download parts are available yet.\n </p>\n </div>\n )}\n\n {pendingAction && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"export-confirm-title\"\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-surface/80 backdrop-blur-sm p-4\"\n >\n <div className=\"w-full max-w-md rounded-xl border-2 border-border-subtle bg-bg-surface p-6 shadow-xl\">\n <h2 id=\"export-confirm-title\" className=\"text-lg font-bold\">\n {pendingAction === \"cancel\" ? \"Cancel export?\" : \"Delete export?\"}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {pendingAction === \"cancel\"\n ? \"This stops the running export job. You can retry it later if needed.\"\n : \"This removes the export job from history. This action cannot be undone.\"}\n </p>\n <div className=\"mt-6 flex flex-col-reverse sm:flex-row sm:justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => setPendingAction(null)}\n className=\"px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium\"\n >\n Keep Export\n </button>\n <button\n type=\"button\"\n onClick={confirmPendingAction}\n disabled={cancelling || deleting}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium disabled:opacity-50\"\n >\n {cancelling || deleting ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : null}\n {pendingAction === \"cancel\" ? \"Cancel Export\" : \"Delete Export\"}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportDetailPage.js","names":[],"sources":["../../../src/export/pages/ExportDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, useNavigate } from \"react-router-dom\";\nimport {\n ArrowLeft,\n Download,\n XCircle,\n Trash2,\n RotateCcw,\n Loader2,\n FileDown,\n Clock,\n AlertCircle,\n CheckCircle2,\n HardDrive,\n FileText,\n} from \"lucide-react\";\nimport {\n GlassCard,\n CTAOverflowMenu,\n IllustratedEmptyState,\n NextSteps,\n type NextStep,\n} from \"@burdenoff/fe-libs/ui\";\nimport type { CTAAction, CTAPrimaryAction } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useExportJob,\n useExportJobProgress,\n useCancelExportJob,\n useDeleteExportJob,\n useRetryExportJob,\n} from \"../hooks/useExportQueries\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { ExportProgressBar } from \"../components/ExportProgressBar\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n getFormatDisplayName,\n getArchiveFormatDisplayName,\n} from \"../constants/enums\";\nimport { cn } from \"../utils/cn\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/**\n * Format bytes to human-readable size\n */\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\n}\n\n/**\n * Helper to join paths correctly\n */\nconst joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n return cleanBase + cleanPath;\n};\n\n/**\n * Export Detail Page\n * Single export job detail view with parts, progress, download links.\n * Uses GraphQL subscriptions for live progress updates when a job is active.\n */\nexport function ExportDetailPage() {\n const { jobId } = useParams<{ jobId: string }>();\n const { basePath = \"/\", navigate } = useExport();\n const routerNavigate = useNavigate();\n\n const handleBack = () => {\n if (window.history.length > 1) {\n routerNavigate(-1);\n } else {\n navigate?.(basePath);\n }\n };\n\n const { data: job, isLoading } = useExportJob(jobId);\n const { data: progressJob } = useExportJobProgress(jobId);\n const [pendingAction, setPendingAction] = useState<\n \"cancel\" | \"delete\" | null\n >(null);\n\n const { cancelJob, isPending: cancelling } = useCancelExportJob();\n const { deleteJob, isPending: deleting } = useDeleteExportJob();\n const { retryJob, isPending: retrying } = useRetryExportJob();\n\n const liveJob = progressJob ?? job ?? null;\n\n // Jobs in CREATED/QUEUED/RUNNING can still be cancelled\n const isCancellable =\n liveJob?.status === \"CREATED\" ||\n liveJob?.status === \"QUEUED\" ||\n liveJob?.status === \"RUNNING\";\n const isCompleted = liveJob?.status === \"COMPLETED\";\n const isFailed = liveJob?.status === \"FAILED\";\n const isRetryable =\n liveJob?.status === \"FAILED\" ||\n liveJob?.status === \"CANCELLED\" ||\n liveJob?.status === \"EXPIRED\";\n\n const handleCancel = async () => {\n if (!jobId) return;\n setPendingAction(\"cancel\");\n };\n\n const handleDelete = async () => {\n if (!jobId) return;\n nativeImpact(\"medium\");\n setPendingAction(\"delete\");\n };\n\n const confirmPendingAction = async () => {\n if (!jobId || !pendingAction) return;\n try {\n if (pendingAction === \"cancel\") {\n await cancelJob(jobId);\n } else {\n await deleteJob(jobId);\n navigate?.(basePath);\n }\n nativeNotify(\"success\");\n } catch {\n nativeNotify(\"error\");\n }\n setPendingAction(null);\n };\n\n const handleRetry = async () => {\n if (!jobId) return;\n const newJob = await retryJob(jobId);\n // Navigate to the new job created by retry\n if (newJob?.id) {\n navigate?.(joinPath(basePath, newJob.id));\n }\n };\n\n // Build a single primary CTA + overflow menu for the header. Download (the\n // first available part) is the primary action when the export is ready;\n // otherwise the most relevant lifecycle action leads. Destructive Delete\n // always lives in the overflow menu and is confirmed via the dialog.\n const firstDownloadablePart = liveJob?.parts.find((p) => p.downloadUrl);\n\n // Loading\n if (isLoading) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20\">\n <Loader2 size={48} className=\"animate-spin text-primary\" />\n <span className=\"mt-6 text-sm font-medium text-text-secondary\">\n Loading export details…\n </span>\n </div>\n );\n }\n\n // Not found\n if (!liveJob) {\n return (\n <PageLayout showHeader={false}>\n <div className=\"flex items-center gap-3 mb-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label=\"Go back\"\n className=\"p-2.5 hover:bg-accent rounded-lg transition-all duration-200\"\n >\n <ArrowLeft size={20} />\n </button>\n <h1 className=\"text-xl font-bold\">Export Not Found</h1>\n </div>\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Export not found\"\n description=\"The export job you are looking for does not exist or has been deleted.\"\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <ArrowLeft size={18} />\n Back to Dashboard\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n // Primary CTA: download when ready, otherwise retry when retryable.\n const primaryAction: CTAPrimaryAction | undefined = firstDownloadablePart\n ? {\n label: \"Download\",\n icon: <Download size={16} />,\n onSelect: () => {\n if (firstDownloadablePart.downloadUrl) {\n window.location.assign(firstDownloadablePart.downloadUrl);\n }\n },\n }\n : isRetryable\n ? {\n label: \"Retry Export\",\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n }\n : undefined;\n\n // Overflow actions: lifecycle controls + destructive delete.\n const overflowActions: CTAAction[] = [];\n if (isRetryable && firstDownloadablePart) {\n overflowActions.push({\n label: \"Retry Export\",\n icon: <RotateCcw size={16} />,\n disabled: retrying,\n onSelect: () => {\n void handleRetry();\n },\n });\n }\n if (isCancellable) {\n overflowActions.push({\n label: \"Cancel Export\",\n icon: <XCircle size={16} />,\n disabled: cancelling,\n onSelect: () => {\n void handleCancel();\n },\n });\n }\n if (!isCancellable) {\n overflowActions.push({\n label: \"Delete Export\",\n icon: <Trash2 size={16} />,\n intent: \"destructive\",\n disabled: deleting,\n onSelect: () => {\n void handleDelete();\n },\n });\n }\n\n // Next-step guidance — concrete actions wired to real handlers/links, tuned\n // to the job's current state so a new user knows what to do from here.\n const nextSteps: NextStep[] = [\n {\n id: \"download\",\n label: \"Download your export\",\n description:\n \"Grab the generated files below before their download links expire.\",\n icon: <Download size={16} />,\n onClick: () => {\n if (firstDownloadablePart?.downloadUrl) {\n window.location.assign(firstDownloadablePart.downloadUrl);\n }\n },\n done: isCompleted,\n },\n ...(isRetryable\n ? [\n {\n id: \"retry\",\n label: \"Retry this export\",\n description:\n \"This export didn't finish — start a fresh run with the same settings.\",\n icon: <RotateCcw size={16} />,\n onClick: () => {\n void handleRetry();\n },\n } as NextStep,\n ]\n : []),\n {\n id: \"history\",\n label: \"Back to History\",\n description: \"See all your exports and track other jobs in progress.\",\n icon: <Clock size={16} />,\n onClick: () => navigate?.(basePath),\n },\n ];\n\n return (\n <PageLayout showHeader={false}>\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl bg-accent-wash border border-border-seam p-6 shadow-[var(--shadow-pop)]\">\n <div className=\"relative z-10 flex flex-col sm:flex-row sm:items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n aria-label=\"Go back\"\n className=\"p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 self-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3 flex-1 min-w-0\">\n <div className=\"p-2.5 rounded-lg bg-action-primary-bg/10\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-xl sm:text-2xl font-bold truncate\">\n {liveJob.name}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {liveJob.entityTypes.join(\", \")}\n </p>\n </div>\n <ExportStatusBadge status={liveJob.status} />\n </div>\n {(primaryAction || overflowActions.length > 0) && (\n <div className=\"flex-shrink-0\">\n <CTAOverflowMenu\n primary={primaryAction}\n actions={overflowActions}\n />\n </div>\n )}\n </div>\n </div>\n\n {/* Progress (for active jobs) */}\n {(liveJob.status === \"RUNNING\" || liveJob.status === \"QUEUED\") && (\n <div className=\"border border-border-seam rounded-xl bg-status-info-bg-subtle p-6\">\n <ExportProgressBar progress={liveJob.progress} size=\"lg\" />\n {liveJob.processedEntities != null &&\n liveJob.totalEntities != null && (\n <p className=\"text-sm text-text-secondary mt-2\">\n {liveJob.processedEntities.toLocaleString()} /{\" \"}\n {liveJob.totalEntities.toLocaleString()} entities processed\n </p>\n )}\n </div>\n )}\n\n {/* Error (for failed jobs) */}\n {isFailed && liveJob.errorMessage && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-6 flex items-start gap-4\">\n <AlertCircle\n size={24}\n className=\"text-status-error-text flex-shrink-0 mt-0.5\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n Export Failed\n </h3>\n <p className=\"text-sm text-status-error-text\">\n {liveJob.errorMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Info Grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {[\n {\n icon: <FileText size={18} />,\n label: \"Format\",\n value: getFormatDisplayName(liveJob.format),\n },\n {\n icon: <HardDrive size={18} />,\n label: \"Archive\",\n value: getArchiveFormatDisplayName(liveJob.archiveFormat),\n },\n {\n icon: <Download size={18} />,\n label: \"Total Size\",\n value:\n liveJob.totalSizeBytes != null\n ? formatBytes(liveJob.totalSizeBytes)\n : \"-\",\n },\n {\n icon: <Clock size={18} />,\n label: \"Created\",\n value: new Date(liveJob.createdAt).toLocaleDateString(),\n },\n ].map((item) => (\n <GlassCard\n key={item.label}\n treatment=\"metric\"\n className=\"p-4 border-border-seam\"\n >\n <div className=\"flex items-center gap-2 text-text-secondary mb-1\">\n {item.icon}\n <span className=\"text-xs font-medium\">{item.label}</span>\n </div>\n <p className=\"font-bold text-lg tabular-nums\">{item.value}</p>\n </GlassCard>\n ))}\n </div>\n\n {/* Next-step guidance for this export (dismissable, per-job) */}\n <NextSteps\n storageKey={`export-detail-${liveJob.id}`}\n title=\"What's next\"\n steps={nextSteps}\n />\n\n {/* Parts */}\n {liveJob.parts.length > 0 && (\n <PageSection\n title=\"Export Parts\"\n description={`${liveJob.parts.length} file(s) generated`}\n >\n <div className=\"space-y-2\">\n {liveJob.parts.map((part) => (\n <div\n key={part.id}\n className={cn(\n \"border border-border-seam rounded-xl p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n )}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileDown size={20} className=\"text-primary flex-shrink-0\" />\n <div className=\"min-w-0\">\n <p className=\"font-medium truncate\">{part.fileName}</p>\n <p className=\"text-sm text-text-secondary\">\n {part.entityTypes.join(\", \")} ·{\" \"}\n {formatBytes(part.sizeBytes)}\n {part.downloadExpiresAt &&\n ` · expires ${new Date(part.downloadExpiresAt).toLocaleDateString()}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {part.downloadUrl ? (\n <a\n href={part.downloadUrl}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors text-sm font-medium\"\n >\n <Download size={14} />\n Download\n </a>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock size={14} />\n Pending\n </span>\n )}\n </div>\n </div>\n ))}\n </div>\n </PageSection>\n )}\n\n {/* Completed message when no parts yet */}\n {isCompleted && liveJob.parts.length === 0 && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-success-bg-subtle p-6 text-center\">\n <CheckCircle2\n size={48}\n className=\"text-status-success-text mx-auto mb-3\"\n />\n <h3 className=\"text-lg font-bold text-status-success-text mb-1\">\n Export Complete\n </h3>\n <p className=\"text-sm text-status-success-text\">\n Your export has completed but no download parts are available yet.\n </p>\n </div>\n )}\n\n {pendingAction && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"export-confirm-title\"\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-surface/80 backdrop-blur-sm p-4\"\n >\n <div className=\"w-full max-w-md rounded-xl border-2 border-border-subtle bg-bg-surface p-6 shadow-xl\">\n <h2 id=\"export-confirm-title\" className=\"text-lg font-bold\">\n {pendingAction === \"cancel\" ? \"Cancel export?\" : \"Delete export?\"}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {pendingAction === \"cancel\"\n ? \"This stops the running export job. You can retry it later if needed.\"\n : \"This removes the export job from history. This action cannot be undone.\"}\n </p>\n <div className=\"mt-6 flex flex-col-reverse sm:flex-row sm:justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => setPendingAction(null)}\n className=\"px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium\"\n >\n Keep Export\n </button>\n <button\n type=\"button\"\n onClick={confirmPendingAction}\n disabled={cancelling || deleting}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium disabled:opacity-50\"\n >\n {cancelling || deleting ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : null}\n {pendingAction === \"cancel\" ? \"Cancel Export\" : \"Delete Export\"}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|