@burdenoff/microfe-export 2026.1013.1 → 2026.1021.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/hooks/useExportQueries.d.ts.map +1 -1
- package/dist/export/hooks/useExportQueries.js +121 -100
- package/dist/export/hooks/useExportQueries.js.map +1 -1
- package/dist/export/pages/CreateExportPage.js +1 -1
- package/dist/export/pages/CreateExportPage.js.map +1 -1
- package/dist/export/pages/ExportHistoryPage.js +2 -2
- package/dist/export/pages/ExportHistoryPage.js.map +1 -1
- package/dist/export/pages/ExportTemplatesPage.js +1 -1
- package/dist/export/pages/ExportTemplatesPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExportQueries.d.ts","sourceRoot":"","sources":["../../../src/export/hooks/useExportQueries.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"useExportQueries.d.ts","sourceRoot":"","sources":["../../../src/export/hooks/useExportQueries.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,106 +2,107 @@ import { useExport as e } from "../providers/ExportProvider.js";
|
|
|
2
2
|
import { CancelExportJobDocument as t, CreateExportJobDocument as n, CreateExportTemplateDocument as r, DeleteExportJobDocument as i, DeleteExportTemplateDocument as a, ExportJobProgressDocument as o, GetAvailableExportEntitiesDocument as s, GetExportJobDocument as c, GetExportTemplateDocument as l, ListExportJobsDocument as u, ListExportTemplatesDocument as d, RetryExportJobDocument as f } from "../../generated/wspace-operations.js";
|
|
3
3
|
import { ExportFormat as p, ExportJobStatus as m, ExportScope as h } from "../../generated/wspace-types.js";
|
|
4
4
|
import { emitExportEvent as g } from "../utils/events.js";
|
|
5
|
-
import {
|
|
5
|
+
import { useEffect as _, useRef as v } from "react";
|
|
6
|
+
import { useMutation as y, useQuery as b, useSubscription as x } from "@apollo/client/react";
|
|
6
7
|
//#region src/export/hooks/useExportQueries.ts
|
|
7
|
-
function
|
|
8
|
+
function S(e) {
|
|
8
9
|
return e ?? void 0;
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
+
function C(e) {
|
|
11
12
|
if (e == null) return;
|
|
12
13
|
let t = typeof e == "number" ? e : Number(e);
|
|
13
14
|
return Number.isFinite(t) ? t : void 0;
|
|
14
15
|
}
|
|
15
|
-
function
|
|
16
|
-
return
|
|
16
|
+
function w(e) {
|
|
17
|
+
return C(e) ?? 0;
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
+
function T(e) {
|
|
19
20
|
return e && typeof e == "object" && !Array.isArray(e) ? e : void 0;
|
|
20
21
|
}
|
|
21
|
-
function
|
|
22
|
+
function E(e) {
|
|
22
23
|
return {
|
|
23
24
|
hasNextPage: e.hasNextPage,
|
|
24
25
|
hasPreviousPage: e.hasPreviousPage,
|
|
25
|
-
startCursor:
|
|
26
|
-
endCursor:
|
|
26
|
+
startCursor: S(e.startCursor),
|
|
27
|
+
endCursor: S(e.endCursor)
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
|
-
function
|
|
30
|
+
function D(e) {
|
|
30
31
|
return {
|
|
31
32
|
id: e.id,
|
|
32
|
-
jobId:
|
|
33
|
+
jobId: S(e.jobId),
|
|
33
34
|
partNumber: e.partNumber,
|
|
34
35
|
fileName: e.fileName,
|
|
35
|
-
sizeBytes:
|
|
36
|
+
sizeBytes: w(e.sizeBytes),
|
|
36
37
|
entityTypes: e.entityTypes,
|
|
37
|
-
downloadUrl:
|
|
38
|
-
downloadExpiresAt:
|
|
39
|
-
checksum:
|
|
40
|
-
createdAt:
|
|
38
|
+
downloadUrl: S(e.downloadUrl),
|
|
39
|
+
downloadExpiresAt: S(e.downloadExpiresAt),
|
|
40
|
+
checksum: S(e.checksum),
|
|
41
|
+
createdAt: S(e.createdAt)
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
|
-
function
|
|
44
|
+
function O(e) {
|
|
44
45
|
return {
|
|
45
46
|
id: e.id,
|
|
46
47
|
name: e.name,
|
|
47
|
-
description:
|
|
48
|
+
description: S(e.description),
|
|
48
49
|
status: e.status,
|
|
49
50
|
progress: e.progress,
|
|
50
51
|
exportScope: e.exportScope ? e.exportScope : void 0,
|
|
51
|
-
organizationId:
|
|
52
|
+
organizationId: S(e.organizationId),
|
|
52
53
|
targetWorkspaceIds: e.targetWorkspaceIds ?? void 0,
|
|
53
54
|
entityTypes: e.entityTypes,
|
|
54
55
|
format: e.format,
|
|
55
56
|
archiveFormat: e.archiveFormat ?? "ZIP",
|
|
56
|
-
totalEntities:
|
|
57
|
-
processedEntities:
|
|
58
|
-
totalSizeBytes:
|
|
59
|
-
parts: e.parts?.map(
|
|
60
|
-
templateId:
|
|
61
|
-
filters:
|
|
62
|
-
errorMessage:
|
|
63
|
-
startedAt:
|
|
64
|
-
completedAt:
|
|
65
|
-
expiresAt:
|
|
57
|
+
totalEntities: C(e.totalEntities),
|
|
58
|
+
processedEntities: C(e.processedEntities),
|
|
59
|
+
totalSizeBytes: C(e.totalSizeBytes),
|
|
60
|
+
parts: e.parts?.map(D) ?? [],
|
|
61
|
+
templateId: S(e.templateId),
|
|
62
|
+
filters: T(e.entityFilters),
|
|
63
|
+
errorMessage: S(e.errorMessage),
|
|
64
|
+
startedAt: S(e.startedAt),
|
|
65
|
+
completedAt: S(e.completedAt),
|
|
66
|
+
expiresAt: S(e.expiresAt),
|
|
66
67
|
createdBy: e.createdBy,
|
|
67
|
-
temporalWorkflowId:
|
|
68
|
-
workspaceId:
|
|
68
|
+
temporalWorkflowId: S(e.temporalWorkflowId),
|
|
69
|
+
workspaceId: S(e.workspaceId),
|
|
69
70
|
createdAt: e.createdAt,
|
|
70
71
|
updatedAt: e.updatedAt
|
|
71
72
|
};
|
|
72
73
|
}
|
|
73
|
-
function
|
|
74
|
+
function k(e) {
|
|
74
75
|
return {
|
|
75
76
|
id: e.id,
|
|
76
77
|
name: e.name,
|
|
77
|
-
description:
|
|
78
|
+
description: S(e.description),
|
|
78
79
|
entityTypes: e.entityTypes,
|
|
79
80
|
format: e.format,
|
|
80
|
-
filters:
|
|
81
|
+
filters: T(e.entityFilters),
|
|
81
82
|
isDefault: e.isDefault,
|
|
82
83
|
createdBy: e.createdBy,
|
|
83
|
-
workspaceId:
|
|
84
|
+
workspaceId: S(e.workspaceId),
|
|
84
85
|
createdAt: e.createdAt,
|
|
85
86
|
updatedAt: e.updatedAt
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
|
-
function
|
|
89
|
+
function A(e) {
|
|
89
90
|
return {
|
|
90
91
|
entityType: e.entityType,
|
|
91
92
|
displayName: e.displayName,
|
|
92
|
-
description:
|
|
93
|
-
estimatedSize:
|
|
93
|
+
description: S(e.description),
|
|
94
|
+
estimatedSize: S(e.estimatedSize),
|
|
94
95
|
supportsDateRange: e.supportsDateRange,
|
|
95
96
|
supportsMediaExport: e.supportsMediaExport,
|
|
96
97
|
enabled: e.enabled
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
|
-
function
|
|
100
|
+
function j(e) {
|
|
100
101
|
if (e === "CSV") return p.Csv;
|
|
101
102
|
if (e === "JSON") return p.Json;
|
|
102
103
|
throw Error(`Unsupported export format: ${e}`);
|
|
103
104
|
}
|
|
104
|
-
function
|
|
105
|
+
function M(e) {
|
|
105
106
|
switch (e) {
|
|
106
107
|
case "CANCELLED": return m.Cancelled;
|
|
107
108
|
case "COMPLETED": return m.Completed;
|
|
@@ -114,7 +115,7 @@ function A(e) {
|
|
|
114
115
|
default: throw Error(`Unsupported export status: ${e}`);
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
function
|
|
118
|
+
function N(e) {
|
|
118
119
|
switch (e) {
|
|
119
120
|
case "INDIVIDUAL": return h.Individual;
|
|
120
121
|
case "WORKSPACE_ADMIN": return h.WorkspaceAdmin;
|
|
@@ -122,12 +123,12 @@ function j(e) {
|
|
|
122
123
|
default: throw Error(`Unsupported export scope: ${e}`);
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
|
-
function
|
|
126
|
-
let { workspaceId: r } = e(), { data: i, loading: a, error: o, refetch: s } =
|
|
126
|
+
function P(t, n) {
|
|
127
|
+
let { workspaceId: r } = e(), { data: i, loading: a, error: o, refetch: s } = b(u, {
|
|
127
128
|
variables: {
|
|
128
129
|
filter: t && r ? {
|
|
129
|
-
status: t.status && t.status !== "all" ? [
|
|
130
|
-
format: t.format && t.format !== "all" ?
|
|
130
|
+
status: t.status && t.status !== "all" ? [M(t.status)] : void 0,
|
|
131
|
+
format: t.format && t.format !== "all" ? j(t.format) : void 0,
|
|
131
132
|
search: t.search ?? void 0
|
|
132
133
|
} : void 0,
|
|
133
134
|
pagination: n,
|
|
@@ -136,16 +137,16 @@ function M(t, n) {
|
|
|
136
137
|
skip: !r
|
|
137
138
|
});
|
|
138
139
|
return {
|
|
139
|
-
data: i?.listExportJobs.edges.map((e) =>
|
|
140
|
+
data: i?.listExportJobs.edges.map((e) => O(e.node)) ?? [],
|
|
140
141
|
totalCount: i?.listExportJobs.totalCount ?? 0,
|
|
141
|
-
pageInfo: i?.listExportJobs.pageInfo ?
|
|
142
|
+
pageInfo: i?.listExportJobs.pageInfo ? E(i.listExportJobs.pageInfo) : null,
|
|
142
143
|
isLoading: a,
|
|
143
144
|
error: o ?? null,
|
|
144
145
|
refetch: s
|
|
145
146
|
};
|
|
146
147
|
}
|
|
147
|
-
function
|
|
148
|
-
let { workspaceId: n } = e(), { data: r, loading: i, error: a, refetch: o } =
|
|
148
|
+
function F(t) {
|
|
149
|
+
let { workspaceId: n } = e(), { data: r, loading: i, error: a, refetch: o } = b(c, {
|
|
149
150
|
variables: {
|
|
150
151
|
id: t ?? "",
|
|
151
152
|
context: { workspaceId: n }
|
|
@@ -153,57 +154,77 @@ function N(t) {
|
|
|
153
154
|
skip: !t || !n
|
|
154
155
|
});
|
|
155
156
|
return {
|
|
156
|
-
data: r?.getExportJob ?
|
|
157
|
+
data: r?.getExportJob ? O(r.getExportJob) : null,
|
|
157
158
|
isLoading: i,
|
|
158
159
|
error: a ?? null,
|
|
159
160
|
refetch: o
|
|
160
161
|
};
|
|
161
162
|
}
|
|
162
|
-
function
|
|
163
|
-
let { data: t, loading: n, error: r } =
|
|
163
|
+
function I(e) {
|
|
164
|
+
let { data: t, loading: n, error: r } = x(o, {
|
|
164
165
|
variables: { jobId: e ?? "" },
|
|
165
166
|
skip: !e
|
|
166
167
|
});
|
|
167
168
|
return {
|
|
168
|
-
data: t?.exportJobProgress ?
|
|
169
|
+
data: t?.exportJobProgress ? O(t.exportJobProgress) : null,
|
|
169
170
|
isLoading: n,
|
|
170
171
|
error: r ?? null
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
|
-
function
|
|
174
|
-
|
|
174
|
+
function L(e, { fetchMoreResult: t }) {
|
|
175
|
+
return t ? { getAvailableExportEntities: {
|
|
176
|
+
...t.getAvailableExportEntities,
|
|
177
|
+
edges: [...e.getAvailableExportEntities.edges, ...t.getAvailableExportEntities.edges]
|
|
178
|
+
} } : e;
|
|
179
|
+
}
|
|
180
|
+
function R(t) {
|
|
181
|
+
let { workspaceId: n } = e(), r = t ?? n, { data: i, loading: a, error: o, refetch: c, fetchMore: l } = b(s, {
|
|
175
182
|
variables: {
|
|
176
183
|
context: { workspaceId: r },
|
|
177
184
|
pagination: { first: 100 }
|
|
178
185
|
},
|
|
179
186
|
skip: !r
|
|
180
|
-
}), u = i?.getAvailableExportEntities
|
|
181
|
-
|
|
182
|
-
|
|
187
|
+
}), u = i?.getAvailableExportEntities, d = u?.edges.map((e) => A(e.node)) ?? [], f = async () => {
|
|
188
|
+
!r || !u?.pageInfo.hasNextPage || await l({
|
|
189
|
+
variables: {
|
|
190
|
+
context: { workspaceId: r },
|
|
191
|
+
pagination: {
|
|
192
|
+
first: 100,
|
|
193
|
+
after: u.pageInfo.endCursor
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
updateQuery: L
|
|
197
|
+
});
|
|
198
|
+
}, p = u?.pageInfo.hasNextPage ?? !1, m = u?.pageInfo.endCursor ?? null, h = v(null);
|
|
199
|
+
return _(() => {
|
|
200
|
+
a || !r || !p || !m || h.current !== m && (h.current = m, l({
|
|
201
|
+
variables: {
|
|
202
|
+
context: { workspaceId: r },
|
|
203
|
+
pagination: {
|
|
204
|
+
first: 100,
|
|
205
|
+
after: m
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
updateQuery: L
|
|
209
|
+
}));
|
|
210
|
+
}, [
|
|
211
|
+
a,
|
|
212
|
+
r,
|
|
213
|
+
p,
|
|
214
|
+
m,
|
|
215
|
+
l
|
|
216
|
+
]), {
|
|
217
|
+
data: d,
|
|
183
218
|
totalCount: u?.totalCount ?? 0,
|
|
184
|
-
pageInfo: u?.pageInfo ?
|
|
185
|
-
loadMore:
|
|
186
|
-
!r || !u?.pageInfo.hasNextPage || await l({
|
|
187
|
-
variables: {
|
|
188
|
-
context: { workspaceId: r },
|
|
189
|
-
pagination: {
|
|
190
|
-
first: 100,
|
|
191
|
-
after: u.pageInfo.endCursor
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
updateQuery: (e, { fetchMoreResult: t }) => t ? { getAvailableExportEntities: {
|
|
195
|
-
...t.getAvailableExportEntities,
|
|
196
|
-
edges: [...e.getAvailableExportEntities.edges, ...t.getAvailableExportEntities.edges]
|
|
197
|
-
} } : e
|
|
198
|
-
});
|
|
199
|
-
},
|
|
219
|
+
pageInfo: u?.pageInfo ? E(u.pageInfo) : null,
|
|
220
|
+
loadMore: f,
|
|
200
221
|
error: o ?? null,
|
|
201
222
|
isLoading: a,
|
|
202
223
|
refetch: c
|
|
203
224
|
};
|
|
204
225
|
}
|
|
205
|
-
function
|
|
206
|
-
let { workspaceId: n } = e(), r = t ?? n, { data: i, loading: a, error: o, refetch: s, fetchMore: c } =
|
|
226
|
+
function z(t) {
|
|
227
|
+
let { workspaceId: n } = e(), r = t ?? n, { data: i, loading: a, error: o, refetch: s, fetchMore: c } = b(d, {
|
|
207
228
|
variables: {
|
|
208
229
|
context: { workspaceId: r },
|
|
209
230
|
pagination: { first: 50 }
|
|
@@ -211,9 +232,9 @@ function I(t) {
|
|
|
211
232
|
skip: !r
|
|
212
233
|
}), l = i?.listExportTemplates;
|
|
213
234
|
return {
|
|
214
|
-
data: l?.edges.map((e) =>
|
|
235
|
+
data: l?.edges.map((e) => k(e.node)) ?? [],
|
|
215
236
|
totalCount: l?.totalCount ?? 0,
|
|
216
|
-
pageInfo: l?.pageInfo ?
|
|
237
|
+
pageInfo: l?.pageInfo ? E(l.pageInfo) : null,
|
|
217
238
|
loadMore: async () => {
|
|
218
239
|
!r || !l?.pageInfo.hasNextPage || await c({
|
|
219
240
|
variables: {
|
|
@@ -234,8 +255,8 @@ function I(t) {
|
|
|
234
255
|
refetch: s
|
|
235
256
|
};
|
|
236
257
|
}
|
|
237
|
-
function
|
|
238
|
-
let { workspaceId: n } = e(), { data: r, loading: i, error: a } =
|
|
258
|
+
function B(t) {
|
|
259
|
+
let { workspaceId: n } = e(), { data: r, loading: i, error: a } = b(l, {
|
|
239
260
|
variables: {
|
|
240
261
|
id: t ?? "",
|
|
241
262
|
context: { workspaceId: n }
|
|
@@ -243,17 +264,17 @@ function L(t) {
|
|
|
243
264
|
skip: !t || !n
|
|
244
265
|
});
|
|
245
266
|
return {
|
|
246
|
-
data: r?.getExportTemplate ?
|
|
267
|
+
data: r?.getExportTemplate ? k(r.getExportTemplate) : null,
|
|
247
268
|
isLoading: i,
|
|
248
269
|
error: a ?? null
|
|
249
270
|
};
|
|
250
271
|
}
|
|
251
|
-
function
|
|
252
|
-
let t = e(), { workspaceId: r } = t, [i, { loading: a, error: o }] =
|
|
272
|
+
function V() {
|
|
273
|
+
let t = e(), { workspaceId: r } = t, [i, { loading: a, error: o }] = y(n), s = async (e) => {
|
|
253
274
|
let { filters: n, ...a } = e, o = {
|
|
254
275
|
...a,
|
|
255
|
-
exportScope: a.exportScope ?
|
|
256
|
-
format:
|
|
276
|
+
exportScope: a.exportScope ? N(a.exportScope) : void 0,
|
|
277
|
+
format: j(a.format),
|
|
257
278
|
entityFilters: n
|
|
258
279
|
}, s = await i({
|
|
259
280
|
variables: {
|
|
@@ -266,7 +287,7 @@ function R() {
|
|
|
266
287
|
}]
|
|
267
288
|
});
|
|
268
289
|
if (!s.data) throw g(t, "export.api.failed", { operation: "createExportJob" }), Error("Failed to create export job");
|
|
269
|
-
return g(t, "export.job.created", { jobId: s.data.createExportJob.id }),
|
|
290
|
+
return g(t, "export.job.created", { jobId: s.data.createExportJob.id }), O(s.data.createExportJob);
|
|
270
291
|
};
|
|
271
292
|
return {
|
|
272
293
|
createJob: s,
|
|
@@ -277,8 +298,8 @@ function R() {
|
|
|
277
298
|
error: o ?? null
|
|
278
299
|
};
|
|
279
300
|
}
|
|
280
|
-
function
|
|
281
|
-
let n = e(), { workspaceId: r } = n, [i, { loading: a, error: o }] =
|
|
301
|
+
function H() {
|
|
302
|
+
let n = e(), { workspaceId: r } = n, [i, { loading: a, error: o }] = y(t), s = async (e) => {
|
|
282
303
|
let t = await i({
|
|
283
304
|
variables: {
|
|
284
305
|
id: e,
|
|
@@ -304,8 +325,8 @@ function z() {
|
|
|
304
325
|
error: o ?? null
|
|
305
326
|
};
|
|
306
327
|
}
|
|
307
|
-
function
|
|
308
|
-
let t = e(), { workspaceId: n } = t, [r, { loading: a, error: o }] =
|
|
328
|
+
function U() {
|
|
329
|
+
let t = e(), { workspaceId: n } = t, [r, { loading: a, error: o }] = y(i), s = async (e) => (await r({
|
|
309
330
|
variables: {
|
|
310
331
|
id: e,
|
|
311
332
|
context: { workspaceId: n }
|
|
@@ -324,8 +345,8 @@ function B() {
|
|
|
324
345
|
error: o ?? null
|
|
325
346
|
};
|
|
326
347
|
}
|
|
327
|
-
function
|
|
328
|
-
let t = e(), { workspaceId: n } = t, [r, { loading: i, error: a }] =
|
|
348
|
+
function W() {
|
|
349
|
+
let t = e(), { workspaceId: n } = t, [r, { loading: i, error: a }] = y(f);
|
|
329
350
|
return {
|
|
330
351
|
retryJob: async (e) => {
|
|
331
352
|
let i = await r({
|
|
@@ -345,20 +366,20 @@ function V() {
|
|
|
345
366
|
return g(t, "export.job.retried", {
|
|
346
367
|
originalJobId: e,
|
|
347
368
|
jobId: i.data.retryExportJob.id
|
|
348
|
-
}),
|
|
369
|
+
}), O(i.data.retryExportJob);
|
|
349
370
|
},
|
|
350
371
|
loading: i,
|
|
351
372
|
isPending: i,
|
|
352
373
|
error: a ?? null
|
|
353
374
|
};
|
|
354
375
|
}
|
|
355
|
-
function
|
|
356
|
-
let t = e(), { workspaceId: n } = t, [i, { loading: a, error: o }] =
|
|
376
|
+
function G() {
|
|
377
|
+
let t = e(), { workspaceId: n } = t, [i, { loading: a, error: o }] = y(r);
|
|
357
378
|
return {
|
|
358
379
|
createTemplate: async (e) => {
|
|
359
380
|
let r = {
|
|
360
381
|
...e,
|
|
361
|
-
format:
|
|
382
|
+
format: j(e.format)
|
|
362
383
|
}, a = await i({
|
|
363
384
|
variables: {
|
|
364
385
|
context: { workspaceId: n },
|
|
@@ -370,15 +391,15 @@ function H() {
|
|
|
370
391
|
}]
|
|
371
392
|
});
|
|
372
393
|
if (!a.data) throw g(t, "export.api.failed", { operation: "createExportTemplate" }), Error("Failed to create export template");
|
|
373
|
-
return g(t, "export.template.created", { templateId: a.data.createExportTemplate.id }),
|
|
394
|
+
return g(t, "export.template.created", { templateId: a.data.createExportTemplate.id }), k(a.data.createExportTemplate);
|
|
374
395
|
},
|
|
375
396
|
loading: a,
|
|
376
397
|
isPending: a,
|
|
377
398
|
error: o ?? null
|
|
378
399
|
};
|
|
379
400
|
}
|
|
380
|
-
function
|
|
381
|
-
let t = e(), { workspaceId: n } = t, [r, { loading: i, error: o }] =
|
|
401
|
+
function K() {
|
|
402
|
+
let t = e(), { workspaceId: n } = t, [r, { loading: i, error: o }] = y(a);
|
|
382
403
|
return {
|
|
383
404
|
deleteTemplate: async (e) => (await r({
|
|
384
405
|
variables: {
|
|
@@ -396,6 +417,6 @@ function U() {
|
|
|
396
417
|
};
|
|
397
418
|
}
|
|
398
419
|
//#endregion
|
|
399
|
-
export {
|
|
420
|
+
export { R as useAvailableEntities, H as useCancelExportJob, V as useCreateExportJob, G as useCreateExportTemplate, U as useDeleteExportJob, K as useDeleteExportTemplate, F as useExportJob, I as useExportJobProgress, P as useExportJobs, B as useExportTemplate, z as useExportTemplates, W as useRetryExportJob };
|
|
400
421
|
|
|
401
422
|
//# sourceMappingURL=useExportQueries.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExportQueries.js","names":[],"sources":["../../../src/export/hooks/useExportQueries.ts"],"sourcesContent":["/**\n * Export Query Hooks\n *\n * Real Apollo Client hooks for all export operations.\n * Keep these documents in sync with src/operations/wspace/export/*.graphql.\n */\n\nimport { useMutation, useQuery, useSubscription } from \"@apollo/client/react\";\nimport {\n CancelExportJobDocument,\n CreateExportJobDocument,\n CreateExportTemplateDocument,\n DeleteExportJobDocument,\n DeleteExportTemplateDocument,\n ExportJobProgressDocument,\n GetAvailableExportEntitiesDocument,\n GetExportJobDocument,\n GetExportTemplateDocument,\n ListExportJobsDocument,\n ListExportTemplatesDocument,\n RetryExportJobDocument,\n type CancelExportJobMutation,\n type CancelExportJobMutationVariables,\n type CreateExportJobMutation,\n type CreateExportJobMutationVariables,\n type CreateExportTemplateMutation,\n type CreateExportTemplateMutationVariables,\n type DeleteExportJobMutation,\n type DeleteExportJobMutationVariables,\n type DeleteExportTemplateMutation,\n type DeleteExportTemplateMutationVariables,\n type ExportJobProgressSubscription,\n type ExportJobProgressSubscriptionVariables,\n type GetAvailableExportEntitiesQuery,\n type GetAvailableExportEntitiesQueryVariables,\n type GetExportJobQuery,\n type GetExportJobQueryVariables,\n type GetExportTemplateQuery,\n type GetExportTemplateQueryVariables,\n type ListExportJobsQuery,\n type ListExportJobsQueryVariables,\n type ListExportTemplatesQuery,\n type ListExportTemplatesQueryVariables,\n type RetryExportJobMutation,\n type RetryExportJobMutationVariables,\n} from \"../../generated/wspace-operations\";\nimport {\n ExportFormat as GqlExportFormat,\n ExportJobStatus as GqlExportJobStatus,\n ExportScope as GqlExportScope,\n type CreateExportJobInput as GqlCreateExportJobInput,\n type CreateExportTemplateInput as GqlCreateExportTemplateInput,\n type ExportJobFilterInput as GqlExportJobFilterInput,\n type ExportPaginationInput as GqlExportPaginationInput,\n} from \"../../generated/wspace-types\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport { emitExportEvent } from \"../utils/events\";\nimport type {\n ExportArchiveFormat,\n ExportFormat,\n ExportJob,\n ExportJobStatus,\n ExportScope,\n ExportTemplate,\n ExportAvailableEntity,\n ExportPart,\n ExportFilter,\n PageInfo,\n} from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Mapping helpers\n// ---------------------------------------------------------------------------\n\ntype Nullable<T> = T | null | undefined;\n\ninterface ExportPartLike {\n id: string;\n jobId?: Nullable<string>;\n partNumber: number;\n fileName: string;\n sizeBytes: number | string;\n entityTypes: string[];\n downloadUrl?: Nullable<string>;\n downloadExpiresAt?: Nullable<string>;\n checksum?: Nullable<string>;\n createdAt?: Nullable<string>;\n}\n\ninterface ExportJobLike {\n id: string;\n name: string;\n description?: Nullable<string>;\n status: string;\n progress: number;\n exportScope?: Nullable<string>;\n organizationId?: Nullable<string>;\n targetWorkspaceIds?: Nullable<string[]>;\n entityTypes: string[];\n format: string;\n archiveFormat?: Nullable<string>;\n totalEntities?: Nullable<number>;\n processedEntities?: Nullable<number>;\n totalSizeBytes?: Nullable<number | string>;\n parts?: Nullable<ExportPartLike[]>;\n templateId?: Nullable<string>;\n entityFilters?: Nullable<Record<string, unknown>>;\n errorMessage?: Nullable<string>;\n startedAt?: Nullable<string>;\n completedAt?: Nullable<string>;\n expiresAt?: Nullable<string>;\n createdBy: string;\n temporalWorkflowId?: Nullable<string>;\n workspaceId?: Nullable<string>;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface ExportTemplateLike {\n id: string;\n name: string;\n description?: Nullable<string>;\n entityTypes: string[];\n format: string;\n entityFilters?: Nullable<Record<string, unknown>>;\n isDefault: boolean;\n workspaceId?: Nullable<string>;\n createdBy: string;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface ExportAvailableEntityLike {\n entityType: string;\n displayName: string;\n description?: Nullable<string>;\n estimatedSize?: Nullable<string>;\n supportsDateRange: boolean;\n supportsMediaExport: boolean;\n enabled: boolean;\n}\n\nfunction optionalString(value: Nullable<string>): string | undefined {\n return value ?? undefined;\n}\n\nfunction optionalNumber(value: Nullable<number | string>): number | undefined {\n if (value == null) return undefined;\n const parsed = typeof value === \"number\" ? value : Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction numberOrZero(value: Nullable<number | string>): number {\n return optionalNumber(value) ?? 0;\n}\n\nfunction optionalRecord(\n value: Nullable<Record<string, unknown>>,\n): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value\n : undefined;\n}\n\nfunction mapPageInfo(pageInfo: {\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n startCursor?: Nullable<string>;\n endCursor?: Nullable<string>;\n}): PageInfo {\n return {\n hasNextPage: pageInfo.hasNextPage,\n hasPreviousPage: pageInfo.hasPreviousPage,\n startCursor: optionalString(pageInfo.startCursor),\n endCursor: optionalString(pageInfo.endCursor),\n };\n}\n\nfunction mapPart(part: ExportPartLike): ExportPart {\n return {\n id: part.id,\n jobId: optionalString(part.jobId),\n partNumber: part.partNumber,\n fileName: part.fileName,\n sizeBytes: numberOrZero(part.sizeBytes),\n entityTypes: part.entityTypes,\n downloadUrl: optionalString(part.downloadUrl),\n downloadExpiresAt: optionalString(part.downloadExpiresAt),\n checksum: optionalString(part.checksum),\n createdAt: optionalString(part.createdAt),\n };\n}\n\nfunction mapJob(job: ExportJobLike): ExportJob {\n return {\n id: job.id,\n name: job.name,\n description: optionalString(job.description),\n status: job.status as ExportJobStatus,\n progress: job.progress,\n exportScope: job.exportScope ? (job.exportScope as ExportScope) : undefined,\n organizationId: optionalString(job.organizationId),\n targetWorkspaceIds: job.targetWorkspaceIds ?? undefined,\n entityTypes: job.entityTypes,\n format: job.format as ExportFormat,\n archiveFormat: (job.archiveFormat ?? \"ZIP\") as ExportArchiveFormat,\n totalEntities: optionalNumber(job.totalEntities),\n processedEntities: optionalNumber(job.processedEntities),\n totalSizeBytes: optionalNumber(job.totalSizeBytes),\n parts: job.parts?.map(mapPart) ?? [],\n templateId: optionalString(job.templateId),\n filters: optionalRecord(job.entityFilters),\n errorMessage: optionalString(job.errorMessage),\n startedAt: optionalString(job.startedAt),\n completedAt: optionalString(job.completedAt),\n expiresAt: optionalString(job.expiresAt),\n createdBy: job.createdBy,\n temporalWorkflowId: optionalString(job.temporalWorkflowId),\n workspaceId: optionalString(job.workspaceId),\n createdAt: job.createdAt,\n updatedAt: job.updatedAt,\n };\n}\n\nfunction mapTemplate(template: ExportTemplateLike): ExportTemplate {\n return {\n id: template.id,\n name: template.name,\n description: optionalString(template.description),\n entityTypes: template.entityTypes,\n format: template.format as ExportFormat,\n filters: optionalRecord(template.entityFilters),\n isDefault: template.isDefault,\n createdBy: template.createdBy,\n workspaceId: optionalString(template.workspaceId),\n createdAt: template.createdAt,\n updatedAt: template.updatedAt,\n };\n}\n\nfunction mapAvailableEntity(\n entity: ExportAvailableEntityLike,\n): ExportAvailableEntity {\n return {\n entityType: entity.entityType,\n displayName: entity.displayName,\n description: optionalString(entity.description),\n estimatedSize: optionalString(entity.estimatedSize),\n supportsDateRange: entity.supportsDateRange,\n supportsMediaExport: entity.supportsMediaExport,\n enabled: entity.enabled,\n };\n}\n\nfunction toGqlFormat(format: ExportFormat | string): GqlExportFormat {\n if (format === \"CSV\") return GqlExportFormat.Csv;\n if (format === \"JSON\") return GqlExportFormat.Json;\n throw new Error(`Unsupported export format: ${format}`);\n}\n\nfunction toGqlStatus(status: ExportJobStatus | string): GqlExportJobStatus {\n switch (status) {\n case \"CANCELLED\":\n return GqlExportJobStatus.Cancelled;\n case \"COMPLETED\":\n return GqlExportJobStatus.Completed;\n case \"CREATED\":\n return GqlExportJobStatus.Created;\n case \"DELETED\":\n return GqlExportJobStatus.Deleted;\n case \"EXPIRED\":\n return GqlExportJobStatus.Expired;\n case \"FAILED\":\n return GqlExportJobStatus.Failed;\n case \"QUEUED\":\n return GqlExportJobStatus.Queued;\n case \"RUNNING\":\n return GqlExportJobStatus.Running;\n default:\n throw new Error(`Unsupported export status: ${status}`);\n }\n}\n\nfunction toGqlScope(scope: ExportScope | string): GqlExportScope {\n switch (scope) {\n case \"INDIVIDUAL\":\n return GqlExportScope.Individual;\n case \"WORKSPACE_ADMIN\":\n return GqlExportScope.WorkspaceAdmin;\n case \"ORG_ADMIN\":\n return GqlExportScope.OrgAdmin;\n default:\n throw new Error(`Unsupported export scope: ${scope}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Query Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Input for creating an export job\n */\nexport interface CreateExportJobInput {\n name: string;\n description?: string;\n exportScope?: ExportScope;\n organizationId?: string;\n entityTypes: string[];\n format: ExportFormat;\n filters?: Record<string, unknown>;\n templateId?: string;\n dateRangeStart?: string | null;\n dateRangeEnd?: string | null;\n}\n\n/**\n * Input for creating an export template\n */\nexport interface CreateExportTemplateInput {\n name: string;\n description?: string;\n entityTypes: string[];\n format: ExportFormat;\n}\n\n/**\n * Filter input for listing export jobs\n */\nexport interface ExportJobFilterInput {\n status?: ExportJobStatus[];\n format?: ExportFormat;\n entityTypes?: string[];\n search?: string;\n dateFrom?: string;\n dateTo?: string;\n}\n\n/**\n * Pagination input for export job list\n */\nexport interface ExportPaginationInput {\n first?: number;\n after?: string;\n last?: number;\n before?: string;\n}\n\n/**\n * Hook to fetch a paginated list of export jobs\n */\nexport function useExportJobs(\n filter?: Partial<ExportFilter>,\n pagination?: ExportPaginationInput,\n) {\n const { workspaceId } = useExport();\n\n // Build the backend filter from the UI filter shape\n const backendFilter: GqlExportJobFilterInput | undefined =\n filter && workspaceId\n ? {\n status:\n filter.status && filter.status !== \"all\"\n ? [toGqlStatus(filter.status)]\n : undefined,\n format:\n filter.format && filter.format !== \"all\"\n ? toGqlFormat(filter.format)\n : undefined,\n search: filter.search ?? undefined,\n }\n : undefined;\n\n const { data, loading, error, refetch } = useQuery<\n ListExportJobsQuery,\n ListExportJobsQueryVariables\n >(ListExportJobsDocument, {\n variables: {\n filter: backendFilter,\n pagination: pagination as GqlExportPaginationInput | undefined,\n context: { workspaceId },\n },\n skip: !workspaceId,\n });\n\n const jobs = data?.listExportJobs.edges.map((e) => mapJob(e.node)) ?? [];\n const totalCount = data?.listExportJobs.totalCount ?? 0;\n const pageInfo = data?.listExportJobs.pageInfo\n ? mapPageInfo(data.listExportJobs.pageInfo)\n : null;\n\n return {\n data: jobs,\n totalCount,\n pageInfo,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to fetch a single export job by ID.\n */\nexport function useExportJob(jobId: string | undefined) {\n const { workspaceId } = useExport();\n\n const { data, loading, error, refetch } = useQuery<\n GetExportJobQuery,\n GetExportJobQueryVariables\n >(GetExportJobDocument, {\n variables: { id: jobId ?? \"\", context: { workspaceId } },\n skip: !jobId || !workspaceId,\n });\n\n const job = data?.getExportJob ? mapJob(data.getExportJob) : null;\n\n return {\n data: job,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to subscribe to live progress updates for an export job.\n */\nexport function useExportJobProgress(jobId: string | undefined) {\n const { data, loading, error } = useSubscription<\n ExportJobProgressSubscription,\n ExportJobProgressSubscriptionVariables\n >(ExportJobProgressDocument, {\n variables: { jobId: jobId ?? \"\" },\n skip: !jobId,\n });\n\n return {\n data: data?.exportJobProgress ? mapJob(data.exportJobProgress) : null,\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to fetch available entity types for export in a workspace\n */\nexport function useAvailableEntities(overrideWorkspaceId?: string) {\n const { workspaceId: ctxWorkspaceId } = useExport();\n const workspaceId = overrideWorkspaceId ?? ctxWorkspaceId;\n\n const { data, loading, error, refetch, fetchMore } = useQuery<\n GetAvailableExportEntitiesQuery,\n GetAvailableExportEntitiesQueryVariables\n >(GetAvailableExportEntitiesDocument, {\n variables: {\n context: { workspaceId },\n pagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const connection = data?.getAvailableExportEntities;\n const entities =\n connection?.edges.map((edge) => mapAvailableEntity(edge.node)) ?? [];\n\n const loadMore = async () => {\n if (!workspaceId || !connection?.pageInfo.hasNextPage) return;\n await fetchMore({\n variables: {\n context: { workspaceId },\n pagination: { first: 100, after: connection.pageInfo.endCursor },\n },\n updateQuery: (previous, { fetchMoreResult }) => {\n if (!fetchMoreResult) return previous;\n return {\n getAvailableExportEntities: {\n ...fetchMoreResult.getAvailableExportEntities,\n edges: [\n ...previous.getAvailableExportEntities.edges,\n ...fetchMoreResult.getAvailableExportEntities.edges,\n ],\n },\n };\n },\n });\n };\n\n return {\n data: entities,\n totalCount: connection?.totalCount ?? 0,\n pageInfo: connection?.pageInfo ? mapPageInfo(connection.pageInfo) : null,\n loadMore,\n error: error ?? null,\n isLoading: loading,\n refetch,\n };\n}\n\n/**\n * Hook to fetch all export templates for the current workspace\n */\nexport function useExportTemplates(overrideWorkspaceId?: string) {\n const { workspaceId: ctxWorkspaceId } = useExport();\n const workspaceId = overrideWorkspaceId ?? ctxWorkspaceId;\n\n const { data, loading, error, refetch, fetchMore } = useQuery<\n ListExportTemplatesQuery,\n ListExportTemplatesQueryVariables\n >(ListExportTemplatesDocument, {\n variables: {\n context: { workspaceId },\n pagination: { first: 50 },\n },\n skip: !workspaceId,\n });\n\n const connection = data?.listExportTemplates;\n const templates =\n connection?.edges.map((edge) => mapTemplate(edge.node)) ?? [];\n\n const loadMore = async () => {\n if (!workspaceId || !connection?.pageInfo.hasNextPage) return;\n await fetchMore({\n variables: {\n context: { workspaceId },\n pagination: { first: 50, after: connection.pageInfo.endCursor },\n },\n updateQuery: (previous, { fetchMoreResult }) => {\n if (!fetchMoreResult) return previous;\n return {\n listExportTemplates: {\n ...fetchMoreResult.listExportTemplates,\n edges: [\n ...previous.listExportTemplates.edges,\n ...fetchMoreResult.listExportTemplates.edges,\n ],\n },\n };\n },\n });\n };\n\n return {\n data: templates,\n totalCount: connection?.totalCount ?? 0,\n pageInfo: connection?.pageInfo ? mapPageInfo(connection.pageInfo) : null,\n loadMore,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to fetch a single export template by ID\n */\nexport function useExportTemplate(templateId: string | undefined) {\n const { workspaceId } = useExport();\n\n const { data, loading, error } = useQuery<\n GetExportTemplateQuery,\n GetExportTemplateQueryVariables\n >(GetExportTemplateDocument, {\n variables: { id: templateId ?? \"\", context: { workspaceId } },\n skip: !templateId || !workspaceId,\n });\n\n return {\n data: data?.getExportTemplate ? mapTemplate(data.getExportTemplate) : null,\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Mutation Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Hook to create a new export job\n */\nexport function useCreateExportJob() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n CreateExportJobMutation,\n CreateExportJobMutationVariables\n >(CreateExportJobDocument);\n\n const createJob = async (input: CreateExportJobInput): Promise<ExportJob> => {\n const { filters, ...restInput } = input;\n const gqlInput: GqlCreateExportJobInput = {\n ...restInput,\n exportScope: restInput.exportScope\n ? toGqlScope(restInput.exportScope)\n : undefined,\n format: toGqlFormat(restInput.format),\n entityFilters: filters,\n };\n\n const result = await mutate({\n variables: {\n context: { workspaceId },\n input: gqlInput,\n },\n refetchQueries: [\n {\n query: ListExportJobsDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n if (!result.data) {\n emitExportEvent(exportContext, \"export.api.failed\", {\n operation: \"createExportJob\",\n });\n throw new Error(\"Failed to create export job\");\n }\n emitExportEvent(exportContext, \"export.job.created\", {\n jobId: result.data.createExportJob.id,\n });\n return mapJob(result.data.createExportJob);\n };\n\n return {\n createJob,\n // Keep legacy names for existing callers\n mutate: createJob,\n mutateAsync: createJob,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to cancel an export job\n */\nexport function useCancelExportJob() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n CancelExportJobMutation,\n CancelExportJobMutationVariables\n >(CancelExportJobDocument);\n\n const cancelJob = async (id: string) => {\n const result = await mutate({\n variables: {\n id,\n context: { workspaceId },\n },\n refetchQueries: [\n {\n query: ListExportJobsDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n if (!result.data) {\n emitExportEvent(exportContext, \"export.api.failed\", {\n operation: \"cancelExportJob\",\n jobId: id,\n });\n throw new Error(\"Failed to cancel export job\");\n }\n emitExportEvent(exportContext, \"export.job.cancelled\", { jobId: id });\n return result.data.cancelExportJob;\n };\n\n return {\n cancelJob,\n mutate: cancelJob,\n mutateAsync: cancelJob,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to delete an export job\n */\nexport function useDeleteExportJob() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n DeleteExportJobMutation,\n DeleteExportJobMutationVariables\n >(DeleteExportJobDocument);\n\n const deleteJob = async (id: string): Promise<boolean> => {\n await mutate({\n variables: {\n id,\n context: { workspaceId },\n },\n refetchQueries: [\n {\n query: ListExportJobsDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n emitExportEvent(exportContext, \"export.job.deleted\", { jobId: id });\n return true;\n };\n\n return {\n deleteJob,\n mutate: deleteJob,\n mutateAsync: deleteJob,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to retry a failed export job\n */\nexport function useRetryExportJob() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n RetryExportJobMutation,\n RetryExportJobMutationVariables\n >(RetryExportJobDocument);\n\n const retryJob = async (id: string): Promise<ExportJob> => {\n const result = await mutate({\n variables: { id, context: { workspaceId } },\n refetchQueries: [\n {\n query: ListExportJobsDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n if (!result.data) {\n emitExportEvent(exportContext, \"export.api.failed\", {\n operation: \"retryExportJob\",\n jobId: id,\n });\n throw new Error(\"Failed to retry export job\");\n }\n emitExportEvent(exportContext, \"export.job.retried\", {\n originalJobId: id,\n jobId: result.data.retryExportJob.id,\n });\n return mapJob(result.data.retryExportJob);\n };\n\n return {\n retryJob,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to create a new export template\n */\nexport function useCreateExportTemplate() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n CreateExportTemplateMutation,\n CreateExportTemplateMutationVariables\n >(CreateExportTemplateDocument);\n\n const createTemplate = async (\n input: CreateExportTemplateInput,\n ): Promise<ExportTemplate> => {\n const gqlInput: GqlCreateExportTemplateInput = {\n ...input,\n format: toGqlFormat(input.format),\n };\n\n const result = await mutate({\n variables: {\n context: { workspaceId },\n input: gqlInput,\n },\n refetchQueries: [\n {\n query: ListExportTemplatesDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n if (!result.data) {\n emitExportEvent(exportContext, \"export.api.failed\", {\n operation: \"createExportTemplate\",\n });\n throw new Error(\"Failed to create export template\");\n }\n emitExportEvent(exportContext, \"export.template.created\", {\n templateId: result.data.createExportTemplate.id,\n });\n return mapTemplate(result.data.createExportTemplate);\n };\n\n return {\n createTemplate,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to delete an export template\n */\nexport function useDeleteExportTemplate() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n DeleteExportTemplateMutation,\n DeleteExportTemplateMutationVariables\n >(DeleteExportTemplateDocument);\n\n const deleteTemplate = async (id: string): Promise<boolean> => {\n await mutate({\n variables: {\n id,\n context: { workspaceId },\n },\n refetchQueries: [\n {\n query: ListExportTemplatesDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n emitExportEvent(exportContext, \"export.template.deleted\", {\n templateId: id,\n });\n return true;\n };\n\n return {\n deleteTemplate,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useExportQueries.js","names":[],"sources":["../../../src/export/hooks/useExportQueries.ts"],"sourcesContent":["/**\n * Export Query Hooks\n *\n * Real Apollo Client hooks for all export operations.\n * Keep these documents in sync with src/operations/wspace/export/*.graphql.\n */\n\nimport { useEffect, useRef } from \"react\";\nimport { useMutation, useQuery, useSubscription } from \"@apollo/client/react\";\nimport {\n CancelExportJobDocument,\n CreateExportJobDocument,\n CreateExportTemplateDocument,\n DeleteExportJobDocument,\n DeleteExportTemplateDocument,\n ExportJobProgressDocument,\n GetAvailableExportEntitiesDocument,\n GetExportJobDocument,\n GetExportTemplateDocument,\n ListExportJobsDocument,\n ListExportTemplatesDocument,\n RetryExportJobDocument,\n type CancelExportJobMutation,\n type CancelExportJobMutationVariables,\n type CreateExportJobMutation,\n type CreateExportJobMutationVariables,\n type CreateExportTemplateMutation,\n type CreateExportTemplateMutationVariables,\n type DeleteExportJobMutation,\n type DeleteExportJobMutationVariables,\n type DeleteExportTemplateMutation,\n type DeleteExportTemplateMutationVariables,\n type ExportJobProgressSubscription,\n type ExportJobProgressSubscriptionVariables,\n type GetAvailableExportEntitiesQuery,\n type GetAvailableExportEntitiesQueryVariables,\n type GetExportJobQuery,\n type GetExportJobQueryVariables,\n type GetExportTemplateQuery,\n type GetExportTemplateQueryVariables,\n type ListExportJobsQuery,\n type ListExportJobsQueryVariables,\n type ListExportTemplatesQuery,\n type ListExportTemplatesQueryVariables,\n type RetryExportJobMutation,\n type RetryExportJobMutationVariables,\n} from \"../../generated/wspace-operations\";\nimport {\n ExportFormat as GqlExportFormat,\n ExportJobStatus as GqlExportJobStatus,\n ExportScope as GqlExportScope,\n type CreateExportJobInput as GqlCreateExportJobInput,\n type CreateExportTemplateInput as GqlCreateExportTemplateInput,\n type ExportJobFilterInput as GqlExportJobFilterInput,\n type ExportPaginationInput as GqlExportPaginationInput,\n} from \"../../generated/wspace-types\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport { emitExportEvent } from \"../utils/events\";\nimport type {\n ExportArchiveFormat,\n ExportFormat,\n ExportJob,\n ExportJobStatus,\n ExportScope,\n ExportTemplate,\n ExportAvailableEntity,\n ExportPart,\n ExportFilter,\n PageInfo,\n} from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Mapping helpers\n// ---------------------------------------------------------------------------\n\ntype Nullable<T> = T | null | undefined;\n\ninterface ExportPartLike {\n id: string;\n jobId?: Nullable<string>;\n partNumber: number;\n fileName: string;\n sizeBytes: number | string;\n entityTypes: string[];\n downloadUrl?: Nullable<string>;\n downloadExpiresAt?: Nullable<string>;\n checksum?: Nullable<string>;\n createdAt?: Nullable<string>;\n}\n\ninterface ExportJobLike {\n id: string;\n name: string;\n description?: Nullable<string>;\n status: string;\n progress: number;\n exportScope?: Nullable<string>;\n organizationId?: Nullable<string>;\n targetWorkspaceIds?: Nullable<string[]>;\n entityTypes: string[];\n format: string;\n archiveFormat?: Nullable<string>;\n totalEntities?: Nullable<number>;\n processedEntities?: Nullable<number>;\n totalSizeBytes?: Nullable<number | string>;\n parts?: Nullable<ExportPartLike[]>;\n templateId?: Nullable<string>;\n entityFilters?: Nullable<Record<string, unknown>>;\n errorMessage?: Nullable<string>;\n startedAt?: Nullable<string>;\n completedAt?: Nullable<string>;\n expiresAt?: Nullable<string>;\n createdBy: string;\n temporalWorkflowId?: Nullable<string>;\n workspaceId?: Nullable<string>;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface ExportTemplateLike {\n id: string;\n name: string;\n description?: Nullable<string>;\n entityTypes: string[];\n format: string;\n entityFilters?: Nullable<Record<string, unknown>>;\n isDefault: boolean;\n workspaceId?: Nullable<string>;\n createdBy: string;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface ExportAvailableEntityLike {\n entityType: string;\n displayName: string;\n description?: Nullable<string>;\n estimatedSize?: Nullable<string>;\n supportsDateRange: boolean;\n supportsMediaExport: boolean;\n enabled: boolean;\n}\n\nfunction optionalString(value: Nullable<string>): string | undefined {\n return value ?? undefined;\n}\n\nfunction optionalNumber(value: Nullable<number | string>): number | undefined {\n if (value == null) return undefined;\n const parsed = typeof value === \"number\" ? value : Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction numberOrZero(value: Nullable<number | string>): number {\n return optionalNumber(value) ?? 0;\n}\n\nfunction optionalRecord(\n value: Nullable<Record<string, unknown>>,\n): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value\n : undefined;\n}\n\nfunction mapPageInfo(pageInfo: {\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n startCursor?: Nullable<string>;\n endCursor?: Nullable<string>;\n}): PageInfo {\n return {\n hasNextPage: pageInfo.hasNextPage,\n hasPreviousPage: pageInfo.hasPreviousPage,\n startCursor: optionalString(pageInfo.startCursor),\n endCursor: optionalString(pageInfo.endCursor),\n };\n}\n\nfunction mapPart(part: ExportPartLike): ExportPart {\n return {\n id: part.id,\n jobId: optionalString(part.jobId),\n partNumber: part.partNumber,\n fileName: part.fileName,\n sizeBytes: numberOrZero(part.sizeBytes),\n entityTypes: part.entityTypes,\n downloadUrl: optionalString(part.downloadUrl),\n downloadExpiresAt: optionalString(part.downloadExpiresAt),\n checksum: optionalString(part.checksum),\n createdAt: optionalString(part.createdAt),\n };\n}\n\nfunction mapJob(job: ExportJobLike): ExportJob {\n return {\n id: job.id,\n name: job.name,\n description: optionalString(job.description),\n status: job.status as ExportJobStatus,\n progress: job.progress,\n exportScope: job.exportScope ? (job.exportScope as ExportScope) : undefined,\n organizationId: optionalString(job.organizationId),\n targetWorkspaceIds: job.targetWorkspaceIds ?? undefined,\n entityTypes: job.entityTypes,\n format: job.format as ExportFormat,\n archiveFormat: (job.archiveFormat ?? \"ZIP\") as ExportArchiveFormat,\n totalEntities: optionalNumber(job.totalEntities),\n processedEntities: optionalNumber(job.processedEntities),\n totalSizeBytes: optionalNumber(job.totalSizeBytes),\n parts: job.parts?.map(mapPart) ?? [],\n templateId: optionalString(job.templateId),\n filters: optionalRecord(job.entityFilters),\n errorMessage: optionalString(job.errorMessage),\n startedAt: optionalString(job.startedAt),\n completedAt: optionalString(job.completedAt),\n expiresAt: optionalString(job.expiresAt),\n createdBy: job.createdBy,\n temporalWorkflowId: optionalString(job.temporalWorkflowId),\n workspaceId: optionalString(job.workspaceId),\n createdAt: job.createdAt,\n updatedAt: job.updatedAt,\n };\n}\n\nfunction mapTemplate(template: ExportTemplateLike): ExportTemplate {\n return {\n id: template.id,\n name: template.name,\n description: optionalString(template.description),\n entityTypes: template.entityTypes,\n format: template.format as ExportFormat,\n filters: optionalRecord(template.entityFilters),\n isDefault: template.isDefault,\n createdBy: template.createdBy,\n workspaceId: optionalString(template.workspaceId),\n createdAt: template.createdAt,\n updatedAt: template.updatedAt,\n };\n}\n\nfunction mapAvailableEntity(\n entity: ExportAvailableEntityLike,\n): ExportAvailableEntity {\n return {\n entityType: entity.entityType,\n displayName: entity.displayName,\n description: optionalString(entity.description),\n estimatedSize: optionalString(entity.estimatedSize),\n supportsDateRange: entity.supportsDateRange,\n supportsMediaExport: entity.supportsMediaExport,\n enabled: entity.enabled,\n };\n}\n\nfunction toGqlFormat(format: ExportFormat | string): GqlExportFormat {\n if (format === \"CSV\") return GqlExportFormat.Csv;\n if (format === \"JSON\") return GqlExportFormat.Json;\n throw new Error(`Unsupported export format: ${format}`);\n}\n\nfunction toGqlStatus(status: ExportJobStatus | string): GqlExportJobStatus {\n switch (status) {\n case \"CANCELLED\":\n return GqlExportJobStatus.Cancelled;\n case \"COMPLETED\":\n return GqlExportJobStatus.Completed;\n case \"CREATED\":\n return GqlExportJobStatus.Created;\n case \"DELETED\":\n return GqlExportJobStatus.Deleted;\n case \"EXPIRED\":\n return GqlExportJobStatus.Expired;\n case \"FAILED\":\n return GqlExportJobStatus.Failed;\n case \"QUEUED\":\n return GqlExportJobStatus.Queued;\n case \"RUNNING\":\n return GqlExportJobStatus.Running;\n default:\n throw new Error(`Unsupported export status: ${status}`);\n }\n}\n\nfunction toGqlScope(scope: ExportScope | string): GqlExportScope {\n switch (scope) {\n case \"INDIVIDUAL\":\n return GqlExportScope.Individual;\n case \"WORKSPACE_ADMIN\":\n return GqlExportScope.WorkspaceAdmin;\n case \"ORG_ADMIN\":\n return GqlExportScope.OrgAdmin;\n default:\n throw new Error(`Unsupported export scope: ${scope}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Query Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Input for creating an export job\n */\nexport interface CreateExportJobInput {\n name: string;\n description?: string;\n exportScope?: ExportScope;\n organizationId?: string;\n entityTypes: string[];\n format: ExportFormat;\n filters?: Record<string, unknown>;\n templateId?: string;\n dateRangeStart?: string | null;\n dateRangeEnd?: string | null;\n}\n\n/**\n * Input for creating an export template\n */\nexport interface CreateExportTemplateInput {\n name: string;\n description?: string;\n entityTypes: string[];\n format: ExportFormat;\n}\n\n/**\n * Filter input for listing export jobs\n */\nexport interface ExportJobFilterInput {\n status?: ExportJobStatus[];\n format?: ExportFormat;\n entityTypes?: string[];\n search?: string;\n dateFrom?: string;\n dateTo?: string;\n}\n\n/**\n * Pagination input for export job list\n */\nexport interface ExportPaginationInput {\n first?: number;\n after?: string;\n last?: number;\n before?: string;\n}\n\n/**\n * Hook to fetch a paginated list of export jobs\n */\nexport function useExportJobs(\n filter?: Partial<ExportFilter>,\n pagination?: ExportPaginationInput,\n) {\n const { workspaceId } = useExport();\n\n // Build the backend filter from the UI filter shape\n const backendFilter: GqlExportJobFilterInput | undefined =\n filter && workspaceId\n ? {\n status:\n filter.status && filter.status !== \"all\"\n ? [toGqlStatus(filter.status)]\n : undefined,\n format:\n filter.format && filter.format !== \"all\"\n ? toGqlFormat(filter.format)\n : undefined,\n search: filter.search ?? undefined,\n }\n : undefined;\n\n const { data, loading, error, refetch } = useQuery<\n ListExportJobsQuery,\n ListExportJobsQueryVariables\n >(ListExportJobsDocument, {\n variables: {\n filter: backendFilter,\n pagination: pagination as GqlExportPaginationInput | undefined,\n context: { workspaceId },\n },\n skip: !workspaceId,\n });\n\n const jobs = data?.listExportJobs.edges.map((e) => mapJob(e.node)) ?? [];\n const totalCount = data?.listExportJobs.totalCount ?? 0;\n const pageInfo = data?.listExportJobs.pageInfo\n ? mapPageInfo(data.listExportJobs.pageInfo)\n : null;\n\n return {\n data: jobs,\n totalCount,\n pageInfo,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to fetch a single export job by ID.\n */\nexport function useExportJob(jobId: string | undefined) {\n const { workspaceId } = useExport();\n\n const { data, loading, error, refetch } = useQuery<\n GetExportJobQuery,\n GetExportJobQueryVariables\n >(GetExportJobDocument, {\n variables: { id: jobId ?? \"\", context: { workspaceId } },\n skip: !jobId || !workspaceId,\n });\n\n const job = data?.getExportJob ? mapJob(data.getExportJob) : null;\n\n return {\n data: job,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to subscribe to live progress updates for an export job.\n */\nexport function useExportJobProgress(jobId: string | undefined) {\n const { data, loading, error } = useSubscription<\n ExportJobProgressSubscription,\n ExportJobProgressSubscriptionVariables\n >(ExportJobProgressDocument, {\n variables: { jobId: jobId ?? \"\" },\n skip: !jobId,\n });\n\n return {\n data: data?.exportJobProgress ? mapJob(data.exportJobProgress) : null,\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/** Apollo `updateQuery` for the catalog: append a fetched page's edges. */\nfunction appendEntityPage(\n previous: GetAvailableExportEntitiesQuery,\n { fetchMoreResult }: { fetchMoreResult?: GetAvailableExportEntitiesQuery },\n): GetAvailableExportEntitiesQuery {\n if (!fetchMoreResult) return previous;\n return {\n getAvailableExportEntities: {\n ...fetchMoreResult.getAvailableExportEntities,\n edges: [\n ...previous.getAvailableExportEntities.edges,\n ...fetchMoreResult.getAvailableExportEntities.edges,\n ],\n },\n };\n}\n\n/**\n * Hook to fetch available entity types for export in a workspace\n */\nexport function useAvailableEntities(overrideWorkspaceId?: string) {\n const { workspaceId: ctxWorkspaceId } = useExport();\n const workspaceId = overrideWorkspaceId ?? ctxWorkspaceId;\n\n const { data, loading, error, refetch, fetchMore } = useQuery<\n GetAvailableExportEntitiesQuery,\n GetAvailableExportEntitiesQueryVariables\n >(GetAvailableExportEntitiesDocument, {\n variables: {\n context: { workspaceId },\n pagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const connection = data?.getAvailableExportEntities;\n const entities =\n connection?.edges.map((edge) => mapAvailableEntity(edge.node)) ?? [];\n\n const loadMore = async () => {\n if (!workspaceId || !connection?.pageInfo.hasNextPage) return;\n await fetchMore({\n variables: {\n context: { workspaceId },\n pagination: { first: 100, after: connection.pageInfo.endCursor },\n },\n updateQuery: appendEntityPage,\n });\n };\n\n // The entity picker and the template editor need the whole catalog (well\n // over one 100-entry page: entities are sorted by display name, so products\n // late in the alphabet, e.g. VibeControls, were never shown). Fetch the\n // remaining pages, once per cursor.\n const hasNextPage = connection?.pageInfo.hasNextPage ?? false;\n const endCursor = connection?.pageInfo.endCursor ?? null;\n const requestedCursor = useRef<string | null>(null);\n useEffect(() => {\n if (loading || !workspaceId || !hasNextPage || !endCursor) return;\n if (requestedCursor.current === endCursor) return;\n requestedCursor.current = endCursor;\n void fetchMore({\n variables: {\n context: { workspaceId },\n pagination: { first: 100, after: endCursor },\n },\n updateQuery: appendEntityPage,\n });\n }, [loading, workspaceId, hasNextPage, endCursor, fetchMore]);\n\n return {\n data: entities,\n totalCount: connection?.totalCount ?? 0,\n pageInfo: connection?.pageInfo ? mapPageInfo(connection.pageInfo) : null,\n loadMore,\n error: error ?? null,\n isLoading: loading,\n refetch,\n };\n}\n\n/**\n * Hook to fetch all export templates for the current workspace\n */\nexport function useExportTemplates(overrideWorkspaceId?: string) {\n const { workspaceId: ctxWorkspaceId } = useExport();\n const workspaceId = overrideWorkspaceId ?? ctxWorkspaceId;\n\n const { data, loading, error, refetch, fetchMore } = useQuery<\n ListExportTemplatesQuery,\n ListExportTemplatesQueryVariables\n >(ListExportTemplatesDocument, {\n variables: {\n context: { workspaceId },\n pagination: { first: 50 },\n },\n skip: !workspaceId,\n });\n\n const connection = data?.listExportTemplates;\n const templates =\n connection?.edges.map((edge) => mapTemplate(edge.node)) ?? [];\n\n const loadMore = async () => {\n if (!workspaceId || !connection?.pageInfo.hasNextPage) return;\n await fetchMore({\n variables: {\n context: { workspaceId },\n pagination: { first: 50, after: connection.pageInfo.endCursor },\n },\n updateQuery: (previous, { fetchMoreResult }) => {\n if (!fetchMoreResult) return previous;\n return {\n listExportTemplates: {\n ...fetchMoreResult.listExportTemplates,\n edges: [\n ...previous.listExportTemplates.edges,\n ...fetchMoreResult.listExportTemplates.edges,\n ],\n },\n };\n },\n });\n };\n\n return {\n data: templates,\n totalCount: connection?.totalCount ?? 0,\n pageInfo: connection?.pageInfo ? mapPageInfo(connection.pageInfo) : null,\n loadMore,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to fetch a single export template by ID\n */\nexport function useExportTemplate(templateId: string | undefined) {\n const { workspaceId } = useExport();\n\n const { data, loading, error } = useQuery<\n GetExportTemplateQuery,\n GetExportTemplateQueryVariables\n >(GetExportTemplateDocument, {\n variables: { id: templateId ?? \"\", context: { workspaceId } },\n skip: !templateId || !workspaceId,\n });\n\n return {\n data: data?.getExportTemplate ? mapTemplate(data.getExportTemplate) : null,\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Mutation Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Hook to create a new export job\n */\nexport function useCreateExportJob() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n CreateExportJobMutation,\n CreateExportJobMutationVariables\n >(CreateExportJobDocument);\n\n const createJob = async (input: CreateExportJobInput): Promise<ExportJob> => {\n const { filters, ...restInput } = input;\n const gqlInput: GqlCreateExportJobInput = {\n ...restInput,\n exportScope: restInput.exportScope\n ? toGqlScope(restInput.exportScope)\n : undefined,\n format: toGqlFormat(restInput.format),\n entityFilters: filters,\n };\n\n const result = await mutate({\n variables: {\n context: { workspaceId },\n input: gqlInput,\n },\n refetchQueries: [\n {\n query: ListExportJobsDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n if (!result.data) {\n emitExportEvent(exportContext, \"export.api.failed\", {\n operation: \"createExportJob\",\n });\n throw new Error(\"Failed to create export job\");\n }\n emitExportEvent(exportContext, \"export.job.created\", {\n jobId: result.data.createExportJob.id,\n });\n return mapJob(result.data.createExportJob);\n };\n\n return {\n createJob,\n // Keep legacy names for existing callers\n mutate: createJob,\n mutateAsync: createJob,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to cancel an export job\n */\nexport function useCancelExportJob() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n CancelExportJobMutation,\n CancelExportJobMutationVariables\n >(CancelExportJobDocument);\n\n const cancelJob = async (id: string) => {\n const result = await mutate({\n variables: {\n id,\n context: { workspaceId },\n },\n refetchQueries: [\n {\n query: ListExportJobsDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n if (!result.data) {\n emitExportEvent(exportContext, \"export.api.failed\", {\n operation: \"cancelExportJob\",\n jobId: id,\n });\n throw new Error(\"Failed to cancel export job\");\n }\n emitExportEvent(exportContext, \"export.job.cancelled\", { jobId: id });\n return result.data.cancelExportJob;\n };\n\n return {\n cancelJob,\n mutate: cancelJob,\n mutateAsync: cancelJob,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to delete an export job\n */\nexport function useDeleteExportJob() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n DeleteExportJobMutation,\n DeleteExportJobMutationVariables\n >(DeleteExportJobDocument);\n\n const deleteJob = async (id: string): Promise<boolean> => {\n await mutate({\n variables: {\n id,\n context: { workspaceId },\n },\n refetchQueries: [\n {\n query: ListExportJobsDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n emitExportEvent(exportContext, \"export.job.deleted\", { jobId: id });\n return true;\n };\n\n return {\n deleteJob,\n mutate: deleteJob,\n mutateAsync: deleteJob,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to retry a failed export job\n */\nexport function useRetryExportJob() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n RetryExportJobMutation,\n RetryExportJobMutationVariables\n >(RetryExportJobDocument);\n\n const retryJob = async (id: string): Promise<ExportJob> => {\n const result = await mutate({\n variables: { id, context: { workspaceId } },\n refetchQueries: [\n {\n query: ListExportJobsDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n if (!result.data) {\n emitExportEvent(exportContext, \"export.api.failed\", {\n operation: \"retryExportJob\",\n jobId: id,\n });\n throw new Error(\"Failed to retry export job\");\n }\n emitExportEvent(exportContext, \"export.job.retried\", {\n originalJobId: id,\n jobId: result.data.retryExportJob.id,\n });\n return mapJob(result.data.retryExportJob);\n };\n\n return {\n retryJob,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to create a new export template\n */\nexport function useCreateExportTemplate() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n CreateExportTemplateMutation,\n CreateExportTemplateMutationVariables\n >(CreateExportTemplateDocument);\n\n const createTemplate = async (\n input: CreateExportTemplateInput,\n ): Promise<ExportTemplate> => {\n const gqlInput: GqlCreateExportTemplateInput = {\n ...input,\n format: toGqlFormat(input.format),\n };\n\n const result = await mutate({\n variables: {\n context: { workspaceId },\n input: gqlInput,\n },\n refetchQueries: [\n {\n query: ListExportTemplatesDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n if (!result.data) {\n emitExportEvent(exportContext, \"export.api.failed\", {\n operation: \"createExportTemplate\",\n });\n throw new Error(\"Failed to create export template\");\n }\n emitExportEvent(exportContext, \"export.template.created\", {\n templateId: result.data.createExportTemplate.id,\n });\n return mapTemplate(result.data.createExportTemplate);\n };\n\n return {\n createTemplate,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to delete an export template\n */\nexport function useDeleteExportTemplate() {\n const exportContext = useExport();\n const { workspaceId } = exportContext;\n const [mutate, { loading, error }] = useMutation<\n DeleteExportTemplateMutation,\n DeleteExportTemplateMutationVariables\n >(DeleteExportTemplateDocument);\n\n const deleteTemplate = async (id: string): Promise<boolean> => {\n await mutate({\n variables: {\n id,\n context: { workspaceId },\n },\n refetchQueries: [\n {\n query: ListExportTemplatesDocument,\n variables: { context: { workspaceId } },\n },\n ],\n });\n emitExportEvent(exportContext, \"export.template.deleted\", {\n templateId: id,\n });\n return true;\n };\n\n return {\n deleteTemplate,\n loading,\n isPending: loading,\n error: error ?? null,\n };\n}\n"],"mappings":"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"}
|
|
@@ -434,7 +434,7 @@ function E() {
|
|
|
434
434
|
}), D.length > 0 ? /* @__PURE__ */ C("select", {
|
|
435
435
|
value: B ?? "",
|
|
436
436
|
onChange: (e) => V(e.target.value || void 0),
|
|
437
|
-
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary",
|
|
437
|
+
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
438
438
|
children: [/* @__PURE__ */ S("option", {
|
|
439
439
|
value: "",
|
|
440
440
|
children: _("export.create.options.noTemplate", "No template")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateExportPage.js","names":[],"sources":["../../../src/export/pages/CreateExportPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n ArrowLeft,\n ArrowRight,\n Download,\n CheckCircle2,\n Loader2,\n FileSpreadsheet,\n FileJson,\n Package,\n CalendarRange,\n User,\n Users,\n Building2,\n Lock,\n} from \"lucide-react\";\nimport { usePermission } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n EmphasisPanel,\n ShowMore,\n PagePurpose,\n IllustratedEmptyState,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useCreateExportJob,\n useAvailableEntities,\n useExportTemplates,\n} from \"../hooks/useExportQueries\";\nimport { getFormatDisplayName } from \"../constants/enums\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { useTr } from \"../hooks/useTr\";\nimport { cn } from \"../utils/cn\";\nimport { isExportEntityDisabled } from \"../utils/disabledModules\";\nimport type {\n ExportAvailableEntity,\n ExportFormat,\n ExportScope,\n} from \"../types\";\n\ntype WizardStep = \"entities\" | \"options\" | \"review\";\n\n// Backend only supports CSV and JSON\nconst SUPPORTED_FORMATS: ExportFormat[] = [\"CSV\", \"JSON\"];\n\nconst FORMAT_ICONS: Record<ExportFormat, typeof FileJson> = {\n CSV: FileSpreadsheet,\n JSON: FileJson,\n};\n\nconst SCOPE_ICONS: Record<ExportScope, typeof User> = {\n INDIVIDUAL: User,\n WORKSPACE_ADMIN: Users,\n ORG_ADMIN: Building2,\n};\n\n/**\n * Create Export Page\n * Multi-step form: select entities, configure options, review & start\n */\n\nexport function CreateExportPage() {\n const { basePath = \"/\", navigate, organizationId } = useExport();\n const tr = useTr();\n const { createJob, isPending } = useCreateExportJob();\n const { data: availableEntities } = useAvailableEntities();\n const { data: templates } = useExportTemplates();\n const [searchParams] = useSearchParams();\n\n // RBAC: Check permissions for admin scopes\n const canExportWorkspaceAdmin = usePermission(\"export:workspace:admin\");\n const canExportOrgAdmin = usePermission(\"export:org:admin\");\n\n const [step, setStep] = useState<WizardStep>(\"entities\");\n const [selectedEntities, setSelectedEntities] = useState<string[]>([]);\n const [exportName, setExportName] = useState(\"\");\n const [selectedFormat, setSelectedFormat] = useState<ExportFormat>(\"CSV\");\n const [selectedScope, setSelectedScope] = useState<ExportScope>(\"INDIVIDUAL\");\n const [selectedTemplateId, setSelectedTemplateId] = useState<\n string | undefined\n >(searchParams.get(\"templateId\") ?? undefined);\n const [dateRangeStart, setDateRangeStart] = useState(\"\");\n const [dateRangeEnd, setDateRangeEnd] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [showQuotaDialog, setShowQuotaDialog] = useState(false);\n\n // Pre-populate form when arriving from \"Use Template\" — apply only once per templateId\n const appliedTemplateRef = useRef<string | null>(null);\n useEffect(() => {\n const paramTemplateId = searchParams.get(\"templateId\");\n if (!paramTemplateId || !templates?.length) return;\n if (appliedTemplateRef.current === paramTemplateId) return;\n const tpl = templates.find((t) => t.id === paramTemplateId);\n if (!tpl) return;\n appliedTemplateRef.current = paramTemplateId;\n setSelectedTemplateId(tpl.id);\n // Skip entity types whose product export is currently disabled.\n setSelectedEntities(\n (tpl.entityTypes ?? []).filter((et) => !isExportEntityDisabled(et)),\n );\n const normalizedFormat = tpl.format?.toUpperCase() as\n | ExportFormat\n | undefined;\n if (normalizedFormat && SUPPORTED_FORMATS.includes(normalizedFormat)) {\n setSelectedFormat(normalizedFormat);\n }\n if (tpl.name) setExportName(tpl.name);\n }, [searchParams, templates]);\n\n const handleBack = () => {\n if (step === \"options\") setStep(\"entities\");\n else if (step === \"review\") setStep(\"options\");\n else navigate?.(basePath);\n };\n\n const handleNext = () => {\n if (step === \"entities\") {\n if (selectedEntities.length === 0) {\n setError(\n tr(\n \"export.create.errors.selectEntity\",\n \"Please select at least one entity type to export\",\n ),\n );\n return;\n }\n setError(null);\n setStep(\"options\");\n } else if (step === \"options\") {\n if (!exportName.trim()) {\n setError(\n tr(\n \"export.create.errors.exportNameRequired\",\n \"Please provide a name for this export\",\n ),\n );\n return;\n }\n setError(null);\n setStep(\"review\");\n }\n };\n\n const handleSubmit = async () => {\n try {\n setError(null);\n await createJob({\n name: exportName,\n entityTypes: selectedEntities,\n format: selectedFormat,\n exportScope: selectedScope,\n organizationId:\n selectedScope === \"ORG_ADMIN\" ? organizationId : undefined,\n templateId: selectedTemplateId,\n dateRangeStart: dateRangeStart\n ? new Date(dateRangeStart).toISOString()\n : null,\n dateRangeEnd: dateRangeEnd\n ? new Date(dateRangeEnd).toISOString()\n : null,\n });\n navigate?.(basePath);\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowQuotaDialog(true);\n setError(\n tr(\n \"export.create.errors.quotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\n setError(\n err instanceof Error\n ? err.message\n : tr(\"export.create.errors.createFailed\", \"Failed to create export\"),\n );\n }\n };\n\n const toggleEntity = (entityType: string) => {\n // Disabled-product entities can't be selected.\n if (isExportEntityDisabled(entityType)) return;\n setSelectedEntities((prev) =>\n prev.includes(entityType)\n ? prev.filter((e) => e !== entityType)\n : [...prev, entityType],\n );\n };\n\n const getScopeLabel = (scope: ExportScope) => {\n switch (scope) {\n case \"INDIVIDUAL\":\n return tr(\"export.create.scope.individual.label\", \"My Data Only\");\n case \"WORKSPACE_ADMIN\":\n return tr(\n \"export.create.scope.workspaceAdmin.label\",\n \"All Workspace Data\",\n );\n case \"ORG_ADMIN\":\n return tr(\n \"export.create.scope.orgAdmin.label\",\n \"All Organization Data\",\n );\n default:\n return scope;\n }\n };\n\n const getScopeDescription = (scope: ExportScope) => {\n switch (scope) {\n case \"INDIVIDUAL\":\n return tr(\n \"export.create.scope.individual.description\",\n \"Export data you personally created across selected modules.\",\n );\n case \"WORKSPACE_ADMIN\":\n return tr(\n \"export.create.scope.workspaceAdmin.description\",\n \"Export all data in this workspace regardless of who created it. Requires workspace admin access.\",\n );\n case \"ORG_ADMIN\":\n return tr(\n \"export.create.scope.orgAdmin.description\",\n \"Export data from every workspace in your organization. Requires org-admin access.\",\n );\n default:\n return \"\";\n }\n };\n\n const fallbackEntityCopy: Record<\n string,\n { label: string; description: string }\n > = {\n users: {\n label: tr(\"export.create.entities.users.label\", \"Users\"),\n description: tr(\n \"export.create.entities.users.description\",\n \"Workspace members and profiles\",\n ),\n },\n projects: {\n label: tr(\"export.create.entities.projects.label\", \"Projects\"),\n description: tr(\n \"export.create.entities.projects.description\",\n \"Projects and their metadata\",\n ),\n },\n tasks: {\n label: tr(\"export.create.entities.tasks.label\", \"Tasks\"),\n description: tr(\n \"export.create.entities.tasks.description\",\n \"Task items and assignments\",\n ),\n },\n files: {\n label: tr(\"export.create.entities.files.label\", \"Files\"),\n description: tr(\n \"export.create.entities.files.description\",\n \"File metadata and references\",\n ),\n },\n conversations: {\n label: tr(\"export.create.entities.conversations.label\", \"Conversations\"),\n description: tr(\n \"export.create.entities.conversations.description\",\n \"Chat messages and threads\",\n ),\n },\n tags: {\n label: tr(\"export.create.entities.tags.label\", \"Tags\"),\n description: tr(\n \"export.create.entities.tags.description\",\n \"Tags and vocabularies\",\n ),\n },\n activity: {\n label: tr(\"export.create.entities.activity.label\", \"Activity Logs\"),\n description: tr(\n \"export.create.entities.activity.description\",\n \"Audit trail and activity history\",\n ),\n },\n calendar: {\n label: tr(\"export.create.entities.calendar.label\", \"Calendar Events\"),\n description: tr(\n \"export.create.entities.calendar.description\",\n \"Events and bookings\",\n ),\n },\n };\n\n // Entities the backend marks available, split so the ones a user can export\n // show first and disabled-product entities are listed (still visible) below.\n const visibleEntities = availableEntities.filter((e) => e.enabled !== false);\n const availableForExport = visibleEntities.filter(\n (e) => !isExportEntityDisabled(e.entityType),\n );\n const unavailableForExport = visibleEntities.filter((e) =>\n isExportEntityDisabled(e.entityType),\n );\n\n const renderEntityCard = (\n entity: ExportAvailableEntity,\n isDisabled: boolean,\n ) => {\n const isSelected =\n !isDisabled && selectedEntities.includes(entity.entityType);\n return (\n <button\n type=\"button\"\n key={entity.entityType}\n onClick={() => toggleEntity(entity.entityType)}\n disabled={isDisabled}\n aria-disabled={isDisabled}\n title={\n isDisabled\n ? tr(\n \"export.create.select.unavailableHint\",\n \"Export for this product isn't available yet\",\n )\n : undefined\n }\n className={cn(\n \"text-left border-2 rounded-xl p-4 transition-all duration-200\",\n isDisabled\n ? \"border-border-subtle/30 opacity-50 cursor-not-allowed\"\n : isSelected\n ? \"border-primary bg-action-primary-bg/5 shadow-md\"\n : \"border-border-subtle/50 hover:border-primary/30 hover:shadow-sm\",\n )}\n >\n <div className=\"flex items-center justify-between mb-2 gap-2\">\n <h3 className=\"font-semibold\">\n {fallbackEntityCopy[entity.entityType]?.label ?? entity.displayName}\n </h3>\n {isDisabled ? (\n <span className=\"shrink-0 text-xs font-medium px-2 py-0.5 rounded-full bg-bg-sunken text-text-secondary\">\n {tr(\"export.create.select.unavailable\", \"Unavailable\")}\n </span>\n ) : (\n isSelected && <CheckCircle2 size={18} className=\"text-primary\" />\n )}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {fallbackEntityCopy[entity.entityType]?.description ??\n entity.description}\n </p>\n {entity.estimatedSize && (\n <p className=\"text-xs text-text-secondary mt-2\">\n ~{entity.estimatedSize}\n </p>\n )}\n </button>\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 items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate?.(basePath)}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n aria-label={tr(\n \"export.create.actions.backToDashboard\",\n \"Back to export dashboard\",\n )}\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2.5 rounded-lg bg-action-primary-bg/10 backdrop-blur-sm\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tr(\"export.create.title\", \"New Export\")}\n </h1>\n </div>\n </div>\n </div>\n\n {/* Page purpose — explains the wizard's job in plain language */}\n <PagePurpose icon={<Download size={16} />}>\n {tr(\n \"export.create.purpose\",\n \"Build a one-off export in three steps: pick which data to include, choose a format and scope, then review and start it. The export runs in the background and produces a downloadable ZIP you can grab from History when it finishes.\",\n )}\n </PagePurpose>\n\n {/* Step Indicator */}\n <div className=\"flex items-center gap-2 justify-center flex-wrap\">\n {([\"entities\", \"options\", \"review\"] as WizardStep[]).map((s, i) => (\n <div key={s} className=\"flex items-center gap-2\">\n {i > 0 && <ArrowRight size={18} className=\"text-text-secondary\" />}\n <div\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 transition-all duration-200\",\n step === s\n ? \"bg-action-primary-bg text-action-primary-text border-primary shadow-[var(--shadow-pop)]\"\n : \"bg-bg-sunken/50 text-text-secondary border-border-seam\",\n )}\n >\n <div className=\"flex items-center justify-center size-6 rounded-full bg-bg-surface/20 font-bold\">\n {i + 1}\n </div>\n <span className=\"hidden sm:inline capitalize\">\n {s === \"entities\"\n ? tr(\"export.create.steps.entities\", \"Select Data\")\n : s === \"options\"\n ? tr(\"export.create.steps.options\", \"Options\")\n : tr(\"export.create.steps.review\", \"Review\")}\n </span>\n </div>\n </div>\n ))}\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-4 text-status-error-text text-sm\">\n {error}\n </div>\n )}\n\n {/* Step 1: Select Entities */}\n {step === \"entities\" && (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-bold\">\n {tr(\"export.create.select.title\", \"Select data to export\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"export.create.select.description\",\n \"Choose which entity types to include in your export.\",\n )}\n </p>\n {visibleEntities.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr(\n \"export.create.select.emptyTitle\",\n \"Nothing to export yet\",\n )}\n description={tr(\n \"export.create.select.empty\",\n \"No exportable entity types are configured for this workspace yet. Once your workspace has data in supported modules, they'll show up here to select.\",\n )}\n />\n ) : (\n <div className=\"space-y-6\">\n {availableForExport.length > 0 && (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {availableForExport.map((entity) =>\n renderEntityCard(entity, false),\n )}\n </div>\n )}\n {unavailableForExport.length > 0 && (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"h-px flex-1 bg-border/60\" />\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr(\n \"export.create.select.unavailableSection\",\n \"Not available yet\",\n )}\n </span>\n <div className=\"h-px flex-1 bg-border/60\" />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {unavailableForExport.map((entity) =>\n renderEntityCard(entity, true),\n )}\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n\n {/* Step 2: Configure Options */}\n {step === \"options\" && (\n <div className=\"space-y-6\">\n <div className=\"border border-border-seam rounded-xl p-6 space-y-5 bg-bg-surface\">\n <h2 className=\"text-lg font-bold\">\n {tr(\"export.create.options.title\", \"Export Options\")}\n </h2>\n\n {/* Scope selection */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.scope\", \"Export Scope\")}\n </label>\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-3\">\n {(\n [\n \"INDIVIDUAL\",\n \"WORKSPACE_ADMIN\",\n \"ORG_ADMIN\",\n ] as ExportScope[]\n ).map((value) => {\n // RBAC: Disable admin scopes if user lacks permission\n const isDisabled =\n (value === \"WORKSPACE_ADMIN\" && !canExportWorkspaceAdmin) ||\n (value === \"ORG_ADMIN\" && !canExportOrgAdmin);\n const Icon = SCOPE_ICONS[value];\n const disabledTitle =\n value === \"WORKSPACE_ADMIN\"\n ? tr(\n \"export.create.scope.workspaceAdmin.required\",\n \"Workspace admin access required\",\n )\n : tr(\n \"export.create.scope.orgAdmin.required\",\n \"Organization admin access required\",\n );\n\n return (\n <button\n key={value}\n type=\"button\"\n onClick={() => !isDisabled && setSelectedScope(value)}\n disabled={isDisabled}\n className={cn(\n \"text-left border-2 rounded-xl p-4 transition-all duration-200\",\n selectedScope === value\n ? \"border-primary bg-action-primary-bg/5 shadow-md\"\n : isDisabled\n ? \"border-border-subtle/30 opacity-50 cursor-not-allowed\"\n : \"border-border-subtle/50 hover:border-primary/30 hover:shadow-sm\",\n )}\n title={isDisabled ? disabledTitle : undefined}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <Icon\n size={18}\n className={\n selectedScope === value\n ? \"text-primary\"\n : isDisabled\n ? \"text-text-secondary/50\"\n : \"text-text-secondary\"\n }\n />\n {selectedScope === value && (\n <CheckCircle2 size={16} className=\"text-primary\" />\n )}\n {isDisabled && (\n <Lock size={14} className=\"text-text-secondary/50\" />\n )}\n </div>\n <p className=\"font-semibold text-sm\">\n {getScopeLabel(value)}\n </p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {getScopeDescription(value)}\n </p>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"exportName\"\n className=\"block text-sm font-semibold mb-2\"\n >\n {tr(\"export.create.options.name\", \"Export Name\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n id=\"exportName\"\n aria-label={tr(\"export.create.options.name\", \"Export Name\")}\n value={exportName}\n onChange={(e) => setExportName(e.target.value)}\n className=\"w-full px-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n placeholder={tr(\n \"export.create.options.namePlaceholder\",\n \"e.g., Monthly User Export\",\n )}\n />\n </div>\n\n {/* Format — only CSV and JSON are supported */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.outputFormat\", \"Output Format\")}\n </label>\n <div className=\"grid grid-cols-2 gap-3\">\n {SUPPORTED_FORMATS.map((format) => {\n const Icon = FORMAT_ICONS[format];\n return (\n <button\n type=\"button\"\n key={format}\n onClick={() => setSelectedFormat(format)}\n className={cn(\n \"flex items-center gap-3 border-2 rounded-lg p-3 transition-all duration-200\",\n selectedFormat === format\n ? \"border-primary bg-action-primary-bg/5\"\n : \"border-border-subtle/50 hover:border-primary/30\",\n )}\n >\n <Icon\n size={18}\n className={\n selectedFormat === format\n ? \"text-primary\"\n : \"text-text-secondary\"\n }\n />\n <span className=\"font-medium text-sm\">\n {getFormatDisplayName(format)}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Archive format — only ZIP is supported */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.archiveFormat\", \"Archive Format\")}\n </label>\n <div className=\"flex gap-3\">\n <div\n className={cn(\n \"flex items-center gap-2 border-2 rounded-lg px-4 py-2 border-primary bg-action-primary-bg/5\",\n )}\n >\n <Package size={16} className=\"text-primary\" />\n <span className=\"font-medium text-sm\">\n {tr(\"export.archive.zip\", \"ZIP\")}\n </span>\n </div>\n </div>\n <p className=\"text-xs text-text-secondary mt-1.5\">\n {tr(\n \"export.create.options.archiveDescription\",\n \"Exports are packaged as ZIP archives.\",\n )}\n </p>\n </div>\n\n {/* Advanced / optional refinements — progressively disclosed */}\n <ShowMore\n collapsedHeight={0}\n label={tr(\n \"export.create.options.advancedToggle\",\n \"Date range & template\",\n )}\n >\n <div className=\"space-y-5 pt-1\">\n {/* Date range filter */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarRange size={15} />\n {tr(\"export.create.options.dateRange\", \"Date Range\")}{\" \"}\n <span className=\"font-normal text-text-secondary\">\n {tr(\"export.create.shared.optional\", \"(optional)\")}\n </span>\n </span>\n </label>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <div>\n <label\n htmlFor=\"dateFrom\"\n className=\"block text-xs text-text-secondary mb-1\"\n >\n {tr(\"export.create.options.dateFrom\", \"From\")}\n </label>\n <input\n type=\"date\"\n id=\"dateFrom\"\n aria-label={tr(\n \"export.create.options.dateFrom\",\n \"From\",\n )}\n value={dateRangeStart}\n onChange={(e) => setDateRangeStart(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary text-sm\"\n />\n </div>\n <div>\n <label\n htmlFor=\"dateTo\"\n className=\"block text-xs text-text-secondary mb-1\"\n >\n {tr(\"export.create.options.dateTo\", \"To\")}\n </label>\n <input\n type=\"date\"\n id=\"dateTo\"\n aria-label={tr(\"export.create.options.dateTo\", \"To\")}\n value={dateRangeEnd}\n min={dateRangeStart}\n onChange={(e) => setDateRangeEnd(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary text-sm\"\n />\n </div>\n </div>\n <p className=\"text-xs text-text-secondary mt-1.5\">\n {tr(\n \"export.create.options.dateRangeDescription\",\n \"Leave blank to export all data regardless of date.\",\n )}\n </p>\n </div>\n\n {/* Optional: Use a template */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\n \"export.create.options.basedOnTemplate\",\n \"Based on Template\",\n )}{\" \"}\n <span className=\"font-normal text-text-secondary\">\n {tr(\"export.create.shared.optional\", \"(optional)\")}\n </span>\n </label>\n {templates.length > 0 ? (\n <select\n value={selectedTemplateId ?? \"\"}\n onChange={(e) =>\n setSelectedTemplateId(e.target.value || undefined)\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"\">\n {tr(\"export.create.options.noTemplate\", \"No template\")}\n </option>\n {templates.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"export.create.options.noTemplates\",\n \"No templates yet.\",\n )}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate?.(basePath + \"/templates\")}\n className=\"text-primary underline-offset-2 hover:underline\"\n >\n {tr(\n \"export.create.options.createTemplateLink\",\n \"Create one\",\n )}\n </button>{\" \"}\n {tr(\n \"export.create.options.reuseTemplateHint\",\n \"to reuse export configurations.\",\n )}\n </p>\n )}\n </div>\n </div>\n </ShowMore>\n </div>\n </div>\n )}\n\n {/* Step 3: Review */}\n {step === \"review\" && (\n <div className=\"space-y-6\">\n <EmphasisPanel className=\"p-6 space-y-4\">\n <h2 className=\"text-lg font-bold flex items-center gap-2\">\n <CheckCircle2 size={20} className=\"text-primary\" />\n {tr(\"export.create.review.title\", \"Review Export\")}\n </h2>\n <dl className=\"space-y-3 text-sm\">\n {[\n {\n label: tr(\"export.create.review.fields.name\", \"Name\"),\n value: exportName,\n },\n {\n label: tr(\"export.create.review.fields.scope\", \"Scope\"),\n value: getScopeLabel(selectedScope),\n },\n {\n label: tr(\n \"export.create.review.fields.entityTypes\",\n \"Entity Types\",\n ),\n value: selectedEntities.join(\", \"),\n },\n {\n label: tr(\"export.create.review.fields.format\", \"Format\"),\n value: getFormatDisplayName(selectedFormat),\n },\n {\n label: tr(\"export.create.review.fields.archive\", \"Archive\"),\n value: tr(\"export.archive.zip\", \"ZIP\"),\n },\n ...(dateRangeStart || dateRangeEnd\n ? [\n {\n label: tr(\n \"export.create.review.fields.dateRange\",\n \"Date Range\",\n ),\n value: [\n dateRangeStart || \"—\",\n dateRangeEnd || \"—\",\n ].join(\" → \"),\n },\n ]\n : []),\n ...(selectedTemplateId\n ? [\n {\n label: tr(\n \"export.create.review.fields.template\",\n \"Template\",\n ),\n value:\n templates.find((t) => t.id === selectedTemplateId)\n ?.name ?? selectedTemplateId,\n },\n ]\n : []),\n ].map(({ label, value }) => (\n <div\n key={label}\n className=\"flex flex-col sm:flex-row sm:justify-between gap-1\"\n >\n <dt className=\"text-text-secondary\">{label}</dt>\n <dd className=\"font-semibold sm:text-right break-words\">\n {value}\n </dd>\n </div>\n ))}\n </dl>\n </EmphasisPanel>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-6 py-3 border-2 border-border-subtle/50 rounded-lg hover:bg-accent hover:border-primary/30 transition-all duration-200 font-medium\"\n >\n {step === \"entities\"\n ? tr(\"export.create.actions.cancel\", \"Cancel\")\n : tr(\"export.create.actions.back\", \"Back\")}\n </button>\n {step === \"review\" ? (\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={isPending}\n className=\"flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-all duration-200 font-bold shadow-lg disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isPending ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <Download size={18} />\n )}\n {tr(\"export.create.actions.startExport\", \"Start Export\")}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={handleNext}\n className=\"flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-all duration-200 font-bold shadow-lg\"\n >\n {tr(\"export.create.actions.next\", \"Next\")}\n <ArrowRight size={18} />\n </button>\n )}\n </div>\n\n <AlertDialog open={showQuotaDialog} onOpenChange={setShowQuotaDialog}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {tr(\"export.create.quotaDialog.title\", \"Export Limit Reached\")}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {tr(\n \"export.create.quotaDialog.description\",\n \"You have reached your export limit for this month.\",\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>\n {tr(\"export.create.quotaDialog.dismiss\", \"Dismiss\")}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={() => window.location.assign(\"/billing\")}\n >\n {tr(\"export.create.quotaDialog.viewBilling\", \"View Billing\")}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateExportPage.js","names":[],"sources":["../../../src/export/pages/CreateExportPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n ArrowLeft,\n ArrowRight,\n Download,\n CheckCircle2,\n Loader2,\n FileSpreadsheet,\n FileJson,\n Package,\n CalendarRange,\n User,\n Users,\n Building2,\n Lock,\n} from \"lucide-react\";\nimport { usePermission } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n EmphasisPanel,\n ShowMore,\n PagePurpose,\n IllustratedEmptyState,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useCreateExportJob,\n useAvailableEntities,\n useExportTemplates,\n} from \"../hooks/useExportQueries\";\nimport { getFormatDisplayName } from \"../constants/enums\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { useTr } from \"../hooks/useTr\";\nimport { cn } from \"../utils/cn\";\nimport { isExportEntityDisabled } from \"../utils/disabledModules\";\nimport type {\n ExportAvailableEntity,\n ExportFormat,\n ExportScope,\n} from \"../types\";\n\ntype WizardStep = \"entities\" | \"options\" | \"review\";\n\n// Backend only supports CSV and JSON\nconst SUPPORTED_FORMATS: ExportFormat[] = [\"CSV\", \"JSON\"];\n\nconst FORMAT_ICONS: Record<ExportFormat, typeof FileJson> = {\n CSV: FileSpreadsheet,\n JSON: FileJson,\n};\n\nconst SCOPE_ICONS: Record<ExportScope, typeof User> = {\n INDIVIDUAL: User,\n WORKSPACE_ADMIN: Users,\n ORG_ADMIN: Building2,\n};\n\n/**\n * Create Export Page\n * Multi-step form: select entities, configure options, review & start\n */\n\nexport function CreateExportPage() {\n const { basePath = \"/\", navigate, organizationId } = useExport();\n const tr = useTr();\n const { createJob, isPending } = useCreateExportJob();\n const { data: availableEntities } = useAvailableEntities();\n const { data: templates } = useExportTemplates();\n const [searchParams] = useSearchParams();\n\n // RBAC: Check permissions for admin scopes\n const canExportWorkspaceAdmin = usePermission(\"export:workspace:admin\");\n const canExportOrgAdmin = usePermission(\"export:org:admin\");\n\n const [step, setStep] = useState<WizardStep>(\"entities\");\n const [selectedEntities, setSelectedEntities] = useState<string[]>([]);\n const [exportName, setExportName] = useState(\"\");\n const [selectedFormat, setSelectedFormat] = useState<ExportFormat>(\"CSV\");\n const [selectedScope, setSelectedScope] = useState<ExportScope>(\"INDIVIDUAL\");\n const [selectedTemplateId, setSelectedTemplateId] = useState<\n string | undefined\n >(searchParams.get(\"templateId\") ?? undefined);\n const [dateRangeStart, setDateRangeStart] = useState(\"\");\n const [dateRangeEnd, setDateRangeEnd] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [showQuotaDialog, setShowQuotaDialog] = useState(false);\n\n // Pre-populate form when arriving from \"Use Template\" — apply only once per templateId\n const appliedTemplateRef = useRef<string | null>(null);\n useEffect(() => {\n const paramTemplateId = searchParams.get(\"templateId\");\n if (!paramTemplateId || !templates?.length) return;\n if (appliedTemplateRef.current === paramTemplateId) return;\n const tpl = templates.find((t) => t.id === paramTemplateId);\n if (!tpl) return;\n appliedTemplateRef.current = paramTemplateId;\n setSelectedTemplateId(tpl.id);\n // Skip entity types whose product export is currently disabled.\n setSelectedEntities(\n (tpl.entityTypes ?? []).filter((et) => !isExportEntityDisabled(et)),\n );\n const normalizedFormat = tpl.format?.toUpperCase() as\n | ExportFormat\n | undefined;\n if (normalizedFormat && SUPPORTED_FORMATS.includes(normalizedFormat)) {\n setSelectedFormat(normalizedFormat);\n }\n if (tpl.name) setExportName(tpl.name);\n }, [searchParams, templates]);\n\n const handleBack = () => {\n if (step === \"options\") setStep(\"entities\");\n else if (step === \"review\") setStep(\"options\");\n else navigate?.(basePath);\n };\n\n const handleNext = () => {\n if (step === \"entities\") {\n if (selectedEntities.length === 0) {\n setError(\n tr(\n \"export.create.errors.selectEntity\",\n \"Please select at least one entity type to export\",\n ),\n );\n return;\n }\n setError(null);\n setStep(\"options\");\n } else if (step === \"options\") {\n if (!exportName.trim()) {\n setError(\n tr(\n \"export.create.errors.exportNameRequired\",\n \"Please provide a name for this export\",\n ),\n );\n return;\n }\n setError(null);\n setStep(\"review\");\n }\n };\n\n const handleSubmit = async () => {\n try {\n setError(null);\n await createJob({\n name: exportName,\n entityTypes: selectedEntities,\n format: selectedFormat,\n exportScope: selectedScope,\n organizationId:\n selectedScope === \"ORG_ADMIN\" ? organizationId : undefined,\n templateId: selectedTemplateId,\n dateRangeStart: dateRangeStart\n ? new Date(dateRangeStart).toISOString()\n : null,\n dateRangeEnd: dateRangeEnd\n ? new Date(dateRangeEnd).toISOString()\n : null,\n });\n navigate?.(basePath);\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowQuotaDialog(true);\n setError(\n tr(\n \"export.create.errors.quotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\n setError(\n err instanceof Error\n ? err.message\n : tr(\"export.create.errors.createFailed\", \"Failed to create export\"),\n );\n }\n };\n\n const toggleEntity = (entityType: string) => {\n // Disabled-product entities can't be selected.\n if (isExportEntityDisabled(entityType)) return;\n setSelectedEntities((prev) =>\n prev.includes(entityType)\n ? prev.filter((e) => e !== entityType)\n : [...prev, entityType],\n );\n };\n\n const getScopeLabel = (scope: ExportScope) => {\n switch (scope) {\n case \"INDIVIDUAL\":\n return tr(\"export.create.scope.individual.label\", \"My Data Only\");\n case \"WORKSPACE_ADMIN\":\n return tr(\n \"export.create.scope.workspaceAdmin.label\",\n \"All Workspace Data\",\n );\n case \"ORG_ADMIN\":\n return tr(\n \"export.create.scope.orgAdmin.label\",\n \"All Organization Data\",\n );\n default:\n return scope;\n }\n };\n\n const getScopeDescription = (scope: ExportScope) => {\n switch (scope) {\n case \"INDIVIDUAL\":\n return tr(\n \"export.create.scope.individual.description\",\n \"Export data you personally created across selected modules.\",\n );\n case \"WORKSPACE_ADMIN\":\n return tr(\n \"export.create.scope.workspaceAdmin.description\",\n \"Export all data in this workspace regardless of who created it. Requires workspace admin access.\",\n );\n case \"ORG_ADMIN\":\n return tr(\n \"export.create.scope.orgAdmin.description\",\n \"Export data from every workspace in your organization. Requires org-admin access.\",\n );\n default:\n return \"\";\n }\n };\n\n const fallbackEntityCopy: Record<\n string,\n { label: string; description: string }\n > = {\n users: {\n label: tr(\"export.create.entities.users.label\", \"Users\"),\n description: tr(\n \"export.create.entities.users.description\",\n \"Workspace members and profiles\",\n ),\n },\n projects: {\n label: tr(\"export.create.entities.projects.label\", \"Projects\"),\n description: tr(\n \"export.create.entities.projects.description\",\n \"Projects and their metadata\",\n ),\n },\n tasks: {\n label: tr(\"export.create.entities.tasks.label\", \"Tasks\"),\n description: tr(\n \"export.create.entities.tasks.description\",\n \"Task items and assignments\",\n ),\n },\n files: {\n label: tr(\"export.create.entities.files.label\", \"Files\"),\n description: tr(\n \"export.create.entities.files.description\",\n \"File metadata and references\",\n ),\n },\n conversations: {\n label: tr(\"export.create.entities.conversations.label\", \"Conversations\"),\n description: tr(\n \"export.create.entities.conversations.description\",\n \"Chat messages and threads\",\n ),\n },\n tags: {\n label: tr(\"export.create.entities.tags.label\", \"Tags\"),\n description: tr(\n \"export.create.entities.tags.description\",\n \"Tags and vocabularies\",\n ),\n },\n activity: {\n label: tr(\"export.create.entities.activity.label\", \"Activity Logs\"),\n description: tr(\n \"export.create.entities.activity.description\",\n \"Audit trail and activity history\",\n ),\n },\n calendar: {\n label: tr(\"export.create.entities.calendar.label\", \"Calendar Events\"),\n description: tr(\n \"export.create.entities.calendar.description\",\n \"Events and bookings\",\n ),\n },\n };\n\n // Entities the backend marks available, split so the ones a user can export\n // show first and disabled-product entities are listed (still visible) below.\n const visibleEntities = availableEntities.filter((e) => e.enabled !== false);\n const availableForExport = visibleEntities.filter(\n (e) => !isExportEntityDisabled(e.entityType),\n );\n const unavailableForExport = visibleEntities.filter((e) =>\n isExportEntityDisabled(e.entityType),\n );\n\n const renderEntityCard = (\n entity: ExportAvailableEntity,\n isDisabled: boolean,\n ) => {\n const isSelected =\n !isDisabled && selectedEntities.includes(entity.entityType);\n return (\n <button\n type=\"button\"\n key={entity.entityType}\n onClick={() => toggleEntity(entity.entityType)}\n disabled={isDisabled}\n aria-disabled={isDisabled}\n title={\n isDisabled\n ? tr(\n \"export.create.select.unavailableHint\",\n \"Export for this product isn't available yet\",\n )\n : undefined\n }\n className={cn(\n \"text-left border-2 rounded-xl p-4 transition-all duration-200\",\n isDisabled\n ? \"border-border-subtle/30 opacity-50 cursor-not-allowed\"\n : isSelected\n ? \"border-primary bg-action-primary-bg/5 shadow-md\"\n : \"border-border-subtle/50 hover:border-primary/30 hover:shadow-sm\",\n )}\n >\n <div className=\"flex items-center justify-between mb-2 gap-2\">\n <h3 className=\"font-semibold\">\n {fallbackEntityCopy[entity.entityType]?.label ?? entity.displayName}\n </h3>\n {isDisabled ? (\n <span className=\"shrink-0 text-xs font-medium px-2 py-0.5 rounded-full bg-bg-sunken text-text-secondary\">\n {tr(\"export.create.select.unavailable\", \"Unavailable\")}\n </span>\n ) : (\n isSelected && <CheckCircle2 size={18} className=\"text-primary\" />\n )}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {fallbackEntityCopy[entity.entityType]?.description ??\n entity.description}\n </p>\n {entity.estimatedSize && (\n <p className=\"text-xs text-text-secondary mt-2\">\n ~{entity.estimatedSize}\n </p>\n )}\n </button>\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 items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate?.(basePath)}\n className=\"p-2.5 hover:bg-accent rounded-lg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n aria-label={tr(\n \"export.create.actions.backToDashboard\",\n \"Back to export dashboard\",\n )}\n >\n <ArrowLeft size={20} />\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2.5 rounded-lg bg-action-primary-bg/10 backdrop-blur-sm\">\n <Download size={24} className=\"text-primary\" />\n </div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tr(\"export.create.title\", \"New Export\")}\n </h1>\n </div>\n </div>\n </div>\n\n {/* Page purpose — explains the wizard's job in plain language */}\n <PagePurpose icon={<Download size={16} />}>\n {tr(\n \"export.create.purpose\",\n \"Build a one-off export in three steps: pick which data to include, choose a format and scope, then review and start it. The export runs in the background and produces a downloadable ZIP you can grab from History when it finishes.\",\n )}\n </PagePurpose>\n\n {/* Step Indicator */}\n <div className=\"flex items-center gap-2 justify-center flex-wrap\">\n {([\"entities\", \"options\", \"review\"] as WizardStep[]).map((s, i) => (\n <div key={s} className=\"flex items-center gap-2\">\n {i > 0 && <ArrowRight size={18} className=\"text-text-secondary\" />}\n <div\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 transition-all duration-200\",\n step === s\n ? \"bg-action-primary-bg text-action-primary-text border-primary shadow-[var(--shadow-pop)]\"\n : \"bg-bg-sunken/50 text-text-secondary border-border-seam\",\n )}\n >\n <div className=\"flex items-center justify-center size-6 rounded-full bg-bg-surface/20 font-bold\">\n {i + 1}\n </div>\n <span className=\"hidden sm:inline capitalize\">\n {s === \"entities\"\n ? tr(\"export.create.steps.entities\", \"Select Data\")\n : s === \"options\"\n ? tr(\"export.create.steps.options\", \"Options\")\n : tr(\"export.create.steps.review\", \"Review\")}\n </span>\n </div>\n </div>\n ))}\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"border-2 border-border-default rounded-xl bg-status-error-bg-subtle p-4 text-status-error-text text-sm\">\n {error}\n </div>\n )}\n\n {/* Step 1: Select Entities */}\n {step === \"entities\" && (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-bold\">\n {tr(\"export.create.select.title\", \"Select data to export\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"export.create.select.description\",\n \"Choose which entity types to include in your export.\",\n )}\n </p>\n {visibleEntities.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr(\n \"export.create.select.emptyTitle\",\n \"Nothing to export yet\",\n )}\n description={tr(\n \"export.create.select.empty\",\n \"No exportable entity types are configured for this workspace yet. Once your workspace has data in supported modules, they'll show up here to select.\",\n )}\n />\n ) : (\n <div className=\"space-y-6\">\n {availableForExport.length > 0 && (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {availableForExport.map((entity) =>\n renderEntityCard(entity, false),\n )}\n </div>\n )}\n {unavailableForExport.length > 0 && (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"h-px flex-1 bg-border/60\" />\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr(\n \"export.create.select.unavailableSection\",\n \"Not available yet\",\n )}\n </span>\n <div className=\"h-px flex-1 bg-border/60\" />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {unavailableForExport.map((entity) =>\n renderEntityCard(entity, true),\n )}\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n\n {/* Step 2: Configure Options */}\n {step === \"options\" && (\n <div className=\"space-y-6\">\n <div className=\"border border-border-seam rounded-xl p-6 space-y-5 bg-bg-surface\">\n <h2 className=\"text-lg font-bold\">\n {tr(\"export.create.options.title\", \"Export Options\")}\n </h2>\n\n {/* Scope selection */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.scope\", \"Export Scope\")}\n </label>\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-3\">\n {(\n [\n \"INDIVIDUAL\",\n \"WORKSPACE_ADMIN\",\n \"ORG_ADMIN\",\n ] as ExportScope[]\n ).map((value) => {\n // RBAC: Disable admin scopes if user lacks permission\n const isDisabled =\n (value === \"WORKSPACE_ADMIN\" && !canExportWorkspaceAdmin) ||\n (value === \"ORG_ADMIN\" && !canExportOrgAdmin);\n const Icon = SCOPE_ICONS[value];\n const disabledTitle =\n value === \"WORKSPACE_ADMIN\"\n ? tr(\n \"export.create.scope.workspaceAdmin.required\",\n \"Workspace admin access required\",\n )\n : tr(\n \"export.create.scope.orgAdmin.required\",\n \"Organization admin access required\",\n );\n\n return (\n <button\n key={value}\n type=\"button\"\n onClick={() => !isDisabled && setSelectedScope(value)}\n disabled={isDisabled}\n className={cn(\n \"text-left border-2 rounded-xl p-4 transition-all duration-200\",\n selectedScope === value\n ? \"border-primary bg-action-primary-bg/5 shadow-md\"\n : isDisabled\n ? \"border-border-subtle/30 opacity-50 cursor-not-allowed\"\n : \"border-border-subtle/50 hover:border-primary/30 hover:shadow-sm\",\n )}\n title={isDisabled ? disabledTitle : undefined}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <Icon\n size={18}\n className={\n selectedScope === value\n ? \"text-primary\"\n : isDisabled\n ? \"text-text-secondary/50\"\n : \"text-text-secondary\"\n }\n />\n {selectedScope === value && (\n <CheckCircle2 size={16} className=\"text-primary\" />\n )}\n {isDisabled && (\n <Lock size={14} className=\"text-text-secondary/50\" />\n )}\n </div>\n <p className=\"font-semibold text-sm\">\n {getScopeLabel(value)}\n </p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {getScopeDescription(value)}\n </p>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"exportName\"\n className=\"block text-sm font-semibold mb-2\"\n >\n {tr(\"export.create.options.name\", \"Export Name\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n id=\"exportName\"\n aria-label={tr(\"export.create.options.name\", \"Export Name\")}\n value={exportName}\n onChange={(e) => setExportName(e.target.value)}\n className=\"w-full px-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n placeholder={tr(\n \"export.create.options.namePlaceholder\",\n \"e.g., Monthly User Export\",\n )}\n />\n </div>\n\n {/* Format — only CSV and JSON are supported */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.outputFormat\", \"Output Format\")}\n </label>\n <div className=\"grid grid-cols-2 gap-3\">\n {SUPPORTED_FORMATS.map((format) => {\n const Icon = FORMAT_ICONS[format];\n return (\n <button\n type=\"button\"\n key={format}\n onClick={() => setSelectedFormat(format)}\n className={cn(\n \"flex items-center gap-3 border-2 rounded-lg p-3 transition-all duration-200\",\n selectedFormat === format\n ? \"border-primary bg-action-primary-bg/5\"\n : \"border-border-subtle/50 hover:border-primary/30\",\n )}\n >\n <Icon\n size={18}\n className={\n selectedFormat === format\n ? \"text-primary\"\n : \"text-text-secondary\"\n }\n />\n <span className=\"font-medium text-sm\">\n {getFormatDisplayName(format)}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Archive format — only ZIP is supported */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\"export.create.options.archiveFormat\", \"Archive Format\")}\n </label>\n <div className=\"flex gap-3\">\n <div\n className={cn(\n \"flex items-center gap-2 border-2 rounded-lg px-4 py-2 border-primary bg-action-primary-bg/5\",\n )}\n >\n <Package size={16} className=\"text-primary\" />\n <span className=\"font-medium text-sm\">\n {tr(\"export.archive.zip\", \"ZIP\")}\n </span>\n </div>\n </div>\n <p className=\"text-xs text-text-secondary mt-1.5\">\n {tr(\n \"export.create.options.archiveDescription\",\n \"Exports are packaged as ZIP archives.\",\n )}\n </p>\n </div>\n\n {/* Advanced / optional refinements — progressively disclosed */}\n <ShowMore\n collapsedHeight={0}\n label={tr(\n \"export.create.options.advancedToggle\",\n \"Date range & template\",\n )}\n >\n <div className=\"space-y-5 pt-1\">\n {/* Date range filter */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarRange size={15} />\n {tr(\"export.create.options.dateRange\", \"Date Range\")}{\" \"}\n <span className=\"font-normal text-text-secondary\">\n {tr(\"export.create.shared.optional\", \"(optional)\")}\n </span>\n </span>\n </label>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <div>\n <label\n htmlFor=\"dateFrom\"\n className=\"block text-xs text-text-secondary mb-1\"\n >\n {tr(\"export.create.options.dateFrom\", \"From\")}\n </label>\n <input\n type=\"date\"\n id=\"dateFrom\"\n aria-label={tr(\n \"export.create.options.dateFrom\",\n \"From\",\n )}\n value={dateRangeStart}\n onChange={(e) => setDateRangeStart(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary text-sm\"\n />\n </div>\n <div>\n <label\n htmlFor=\"dateTo\"\n className=\"block text-xs text-text-secondary mb-1\"\n >\n {tr(\"export.create.options.dateTo\", \"To\")}\n </label>\n <input\n type=\"date\"\n id=\"dateTo\"\n aria-label={tr(\"export.create.options.dateTo\", \"To\")}\n value={dateRangeEnd}\n min={dateRangeStart}\n onChange={(e) => setDateRangeEnd(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary text-sm\"\n />\n </div>\n </div>\n <p className=\"text-xs text-text-secondary mt-1.5\">\n {tr(\n \"export.create.options.dateRangeDescription\",\n \"Leave blank to export all data regardless of date.\",\n )}\n </p>\n </div>\n\n {/* Optional: Use a template */}\n <div>\n <label className=\"block text-sm font-semibold mb-2\">\n {tr(\n \"export.create.options.basedOnTemplate\",\n \"Based on Template\",\n )}{\" \"}\n <span className=\"font-normal text-text-secondary\">\n {tr(\"export.create.shared.optional\", \"(optional)\")}\n </span>\n </label>\n {templates.length > 0 ? (\n <select\n value={selectedTemplateId ?? \"\"}\n onChange={(e) =>\n setSelectedTemplateId(e.target.value || undefined)\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">\n {tr(\"export.create.options.noTemplate\", \"No template\")}\n </option>\n {templates.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"export.create.options.noTemplates\",\n \"No templates yet.\",\n )}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate?.(basePath + \"/templates\")}\n className=\"text-primary underline-offset-2 hover:underline\"\n >\n {tr(\n \"export.create.options.createTemplateLink\",\n \"Create one\",\n )}\n </button>{\" \"}\n {tr(\n \"export.create.options.reuseTemplateHint\",\n \"to reuse export configurations.\",\n )}\n </p>\n )}\n </div>\n </div>\n </ShowMore>\n </div>\n </div>\n )}\n\n {/* Step 3: Review */}\n {step === \"review\" && (\n <div className=\"space-y-6\">\n <EmphasisPanel className=\"p-6 space-y-4\">\n <h2 className=\"text-lg font-bold flex items-center gap-2\">\n <CheckCircle2 size={20} className=\"text-primary\" />\n {tr(\"export.create.review.title\", \"Review Export\")}\n </h2>\n <dl className=\"space-y-3 text-sm\">\n {[\n {\n label: tr(\"export.create.review.fields.name\", \"Name\"),\n value: exportName,\n },\n {\n label: tr(\"export.create.review.fields.scope\", \"Scope\"),\n value: getScopeLabel(selectedScope),\n },\n {\n label: tr(\n \"export.create.review.fields.entityTypes\",\n \"Entity Types\",\n ),\n value: selectedEntities.join(\", \"),\n },\n {\n label: tr(\"export.create.review.fields.format\", \"Format\"),\n value: getFormatDisplayName(selectedFormat),\n },\n {\n label: tr(\"export.create.review.fields.archive\", \"Archive\"),\n value: tr(\"export.archive.zip\", \"ZIP\"),\n },\n ...(dateRangeStart || dateRangeEnd\n ? [\n {\n label: tr(\n \"export.create.review.fields.dateRange\",\n \"Date Range\",\n ),\n value: [\n dateRangeStart || \"—\",\n dateRangeEnd || \"—\",\n ].join(\" → \"),\n },\n ]\n : []),\n ...(selectedTemplateId\n ? [\n {\n label: tr(\n \"export.create.review.fields.template\",\n \"Template\",\n ),\n value:\n templates.find((t) => t.id === selectedTemplateId)\n ?.name ?? selectedTemplateId,\n },\n ]\n : []),\n ].map(({ label, value }) => (\n <div\n key={label}\n className=\"flex flex-col sm:flex-row sm:justify-between gap-1\"\n >\n <dt className=\"text-text-secondary\">{label}</dt>\n <dd className=\"font-semibold sm:text-right break-words\">\n {value}\n </dd>\n </div>\n ))}\n </dl>\n </EmphasisPanel>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-6 py-3 border-2 border-border-subtle/50 rounded-lg hover:bg-accent hover:border-primary/30 transition-all duration-200 font-medium\"\n >\n {step === \"entities\"\n ? tr(\"export.create.actions.cancel\", \"Cancel\")\n : tr(\"export.create.actions.back\", \"Back\")}\n </button>\n {step === \"review\" ? (\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={isPending}\n className=\"flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-all duration-200 font-bold shadow-lg disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isPending ? (\n <Loader2 size={18} className=\"animate-spin\" />\n ) : (\n <Download size={18} />\n )}\n {tr(\"export.create.actions.startExport\", \"Start Export\")}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={handleNext}\n className=\"flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-all duration-200 font-bold shadow-lg\"\n >\n {tr(\"export.create.actions.next\", \"Next\")}\n <ArrowRight size={18} />\n </button>\n )}\n </div>\n\n <AlertDialog open={showQuotaDialog} onOpenChange={setShowQuotaDialog}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {tr(\"export.create.quotaDialog.title\", \"Export Limit Reached\")}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {tr(\n \"export.create.quotaDialog.description\",\n \"You have reached your export limit for this month.\",\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>\n {tr(\"export.create.quotaDialog.dismiss\", \"Dismiss\")}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={() => window.location.assign(\"/billing\")}\n >\n {tr(\"export.create.quotaDialog.viewBilling\", \"View Billing\")}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -109,7 +109,7 @@ function D() {
|
|
|
109
109
|
}), /* @__PURE__ */ C("select", {
|
|
110
110
|
value: A.status,
|
|
111
111
|
onChange: (e) => j({ status: e.target.value }),
|
|
112
|
-
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary",
|
|
112
|
+
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
113
113
|
children: [/* @__PURE__ */ S("option", {
|
|
114
114
|
value: "all",
|
|
115
115
|
children: k("export.history.filters.status.all", "All Statuses")
|
|
@@ -126,7 +126,7 @@ function D() {
|
|
|
126
126
|
}), /* @__PURE__ */ C("select", {
|
|
127
127
|
value: A.format,
|
|
128
128
|
onChange: (e) => j({ format: e.target.value }),
|
|
129
|
-
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary",
|
|
129
|
+
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
130
130
|
children: [
|
|
131
131
|
/* @__PURE__ */ S("option", {
|
|
132
132
|
value: "all",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportHistoryPage.js","names":[],"sources":["../../../src/export/pages/ExportHistoryPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport {\n Search,\n X,\n Clock,\n Download,\n ChevronRight,\n ChevronLeft,\n RefreshCw,\n AlertCircle,\n Filter,\n} from \"lucide-react\";\nimport { IllustratedEmptyState, PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport { useExportJobs } from \"../hooks/useExportQueries\";\nimport { useExportStore } from \"../store/exportStore\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { useTr } from \"../hooks/useTr\";\nimport { cn } from \"../utils/cn\";\nimport {\n getFormatDisplayName,\n getAllStatuses,\n getStatusDisplayName,\n} from \"../constants/enums\";\nimport type { ExportJobStatus, ExportFormat } from \"../types\";\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 History Page\n * Table/list of all past exports with filters and cursor-based pagination\n */\nexport function ExportHistoryPage() {\n const { basePath = \"/\", navigate } = useExport();\n const tr = useTr();\n const { jobFilter, updateFilter, resetFilter, pagination, setPagination } =\n useExportStore();\n const [searchParams] = useSearchParams();\n\n // Initialize status filter from URL search params (e.g. ?status=RUNNING) — once\n const didInitFromUrlRef = useRef(false);\n useEffect(() => {\n if (didInitFromUrlRef.current) return;\n didInitFromUrlRef.current = true;\n const statusParam = searchParams.get(\"status\");\n if (statusParam) {\n updateFilter({\n status: statusParam as ExportJobStatus,\n });\n }\n }, [searchParams, updateFilter]);\n\n const [showFilters, setShowFilters] = useState(false);\n // Cursor stack: index 0 = first page (no cursor), subsequent entries are \"after\" cursors\n const [cursorStack, setCursorStack] = useState<(string | null)[]>([null]);\n const [currentPageIndex, setCurrentPageIndex] = useState(0);\n\n const currentCursor = cursorStack[currentPageIndex] ?? null;\n\n const {\n data: jobs,\n totalCount,\n pageInfo,\n isLoading,\n error,\n refetch,\n } = useExportJobs(jobFilter, {\n first: pagination.pageSize,\n after: currentCursor ?? undefined,\n });\n\n // Client-side search filter (server-side filtering handles status/format)\n const filteredJobs = useMemo(() => {\n if (!jobFilter.search) return jobs;\n const q = jobFilter.search.toLowerCase();\n return jobs.filter(\n (job) =>\n job.name.toLowerCase().includes(q) ||\n job.entityTypes.some((e) => e.toLowerCase().includes(q)),\n );\n }, [jobs, jobFilter.search]);\n\n const hasActiveFilters =\n jobFilter.search ||\n jobFilter.status !== \"all\" ||\n jobFilter.format !== \"all\";\n\n const handleJobClick = (jobId: string) => {\n navigate?.(joinPath(basePath, jobId));\n };\n\n const handleNextPage = () => {\n if (!pageInfo?.hasNextPage || !pageInfo.endCursor) return;\n const newStack = [\n ...cursorStack.slice(0, currentPageIndex + 1),\n pageInfo.endCursor,\n ];\n setCursorStack(newStack);\n setCurrentPageIndex((prev) => prev + 1);\n setPagination({ page: pagination.page + 1, cursor: pageInfo.endCursor });\n };\n\n const handlePrevPage = () => {\n if (currentPageIndex === 0) return;\n const prevIndex = currentPageIndex - 1;\n setCurrentPageIndex(prevIndex);\n setPagination({\n page: pagination.page - 1,\n cursor: cursorStack[prevIndex] ?? null,\n });\n };\n\n const handleResetFilters = () => {\n resetFilter();\n setCursorStack([null]);\n setCurrentPageIndex(0);\n };\n\n const hasPrevPage = currentPageIndex > 0;\n const hasNextPage = pageInfo?.hasNextPage ?? false;\n const pageStart = currentPageIndex * pagination.pageSize + 1;\n const pageEnd = Math.min(\n (currentPageIndex + 1) * pagination.pageSize,\n totalCount,\n );\n const getStatusLabel = (status: ExportJobStatus) =>\n tr(\n `export.shared.status.${status.toLowerCase()}`,\n getStatusDisplayName(status),\n );\n\n return (\n <PageLayout\n title={tr(\"export.history.title\", \"Export History\")}\n subtitle={tr(\n \"export.history.subtitle\",\n \"View and manage all your past export jobs\",\n )}\n icon={<Clock size={24} />}\n headerActions={\n <button\n type=\"button\"\n onClick={() => navigate?.(joinPath(basePath, \"new\"))}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Download size={16} />\n {tr(\"export.history.actions.newExport\", \"New Export\")}\n </button>\n }\n >\n {/* Page purpose — what History is for */}\n <PagePurpose icon={<Clock size={16} />}>\n {tr(\n \"export.history.purpose\",\n \"Every export you've ever started lives here. Track jobs as they run, see which finished or failed, and grab download links for completed exports before they expire. Search by name or entity type, or filter by status and format to find a specific one.\",\n )}\n </PagePurpose>\n\n {/* Search & Filters */}\n <div className=\"space-y-4\">\n <div className=\"flex flex-col sm:flex-row gap-3\">\n {/* Search */}\n <div className=\"flex-1 relative group\">\n <Search\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors\"\n />\n <input\n type=\"text\"\n aria-label={tr(\n \"export.history.search.placeholder\",\n \"Search exports by name or entity type\",\n )}\n placeholder={tr(\n \"export.history.search.placeholder\",\n \"Search exports by name or entity type...\",\n )}\n value={jobFilter.search}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-[3.25rem] pr-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n />\n </div>\n\n {/* Filter Toggle */}\n <button\n type=\"button\"\n onClick={() => setShowFilters(!showFilters)}\n className={cn(\n \"inline-flex items-center gap-2 px-4 py-2.5 border-2 rounded-lg transition-all duration-200 font-medium\",\n showFilters || hasActiveFilters\n ? \"bg-action-primary-bg text-action-primary-text border-primary\"\n : \"border-border-subtle/50 hover:border-primary/30\",\n )}\n >\n <Filter size={16} />\n {showFilters\n ? tr(\"export.history.filters.hide\", \"Hide\")\n : tr(\"export.history.filters.show\", \"Filter\")}\n </button>\n </div>\n\n {/* Filter Panel */}\n {showFilters && (\n <div className=\"border border-border-seam rounded-xl p-4 animate-in fade-in-0 duration-200\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"font-semibold flex items-center gap-2\">\n <Filter size={16} className=\"text-primary\" />\n {tr(\"export.history.filters.title\", \"Filters\")}\n </h3>\n {hasActiveFilters && (\n <button\n type=\"button\"\n onClick={handleResetFilters}\n className=\"text-sm text-text-secondary hover:text-status-error-text flex items-center gap-1\"\n >\n <X size={14} />\n {tr(\"export.history.filters.clearAll\", \"Clear All\")}\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n {/* Status Filter */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium\">\n {tr(\"export.history.filters.status.label\", \"Status\")}\n </label>\n <select\n value={jobFilter.status}\n onChange={(e) =>\n updateFilter({\n status: e.target.value as ExportJobStatus | \"all\",\n })\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"all\">\n {tr(\"export.history.filters.status.all\", \"All Statuses\")}\n </option>\n {getAllStatuses().map((status) => (\n <option key={status} value={status}>\n {getStatusLabel(status)}\n </option>\n ))}\n </select>\n </div>\n\n {/* Format Filter — only CSV and JSON are supported */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium\">\n {tr(\"export.history.filters.format.label\", \"Format\")}\n </label>\n <select\n value={jobFilter.format}\n onChange={(e) =>\n updateFilter({\n format: e.target.value as ExportFormat | \"all\",\n })\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\"\n >\n <option value=\"all\">\n {tr(\"export.history.filters.format.all\", \"All Formats\")}\n </option>\n <option value=\"CSV\">{tr(\"export.format.csv\", \"CSV\")}</option>\n <option value=\"JSON\">\n {tr(\"export.format.json\", \"JSON\")}\n </option>\n </select>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Error State */}\n {error && (\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\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n {tr(\"export.history.error.title\", \"Error loading exports\")}\n </h3>\n <p className=\"text-sm text-status-error-text mb-3\">\n {error.message}\n </p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors\"\n >\n <RefreshCw size={14} />\n {tr(\"export.history.error.retry\", \"Try Again\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Loading */}\n {isLoading && (\n <div className=\"space-y-3\">\n {[...Array(5)].map((_, i) => (\n <div key={i} className=\"border border-border-seam rounded-xl p-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"space-y-2\">\n <div className=\"h-4 w-48 bg-bg-sunken rounded animate-vc-shimmer\" />\n <div className=\"h-3 w-32 bg-bg-sunken rounded animate-vc-shimmer\" />\n </div>\n <div className=\"h-5 w-20 bg-bg-sunken rounded-full animate-vc-shimmer\" />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Jobs List */}\n {!isLoading &&\n !error &&\n (filteredJobs.length > 0 ? (\n <>\n <div className=\"space-y-2\">\n {filteredJobs.map((job, index) => (\n <button\n type=\"button\"\n key={job.id}\n onClick={() => handleJobClick(job.id)}\n className={cn(\n \"w-full text-left border border-border-seam rounded-xl p-4 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\",\n \"group animate-in fade-in-0\",\n )}\n style={{ animationDelay: `${index * 30}ms` }}\n >\n <div className=\"flex items-start sm:items-center justify-between gap-3\">\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-semibold text-text-primary truncate\">\n {job.name}\n </h4>\n <p className=\"text-sm text-text-secondary mt-1 line-clamp-2\">\n {job.entityTypes.join(\", \")} ·{\" \"}\n {getFormatDisplayName(job.format)}\n {job.createdAt &&\n ` · ${new Date(job.createdAt).toLocaleDateString()}`}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <ExportStatusBadge status={job.status} />\n <ChevronRight\n size={18}\n className=\"text-text-secondary group-hover:text-primary transition-colors hidden sm:block\"\n />\n </div>\n </div>\n </button>\n ))}\n </div>\n\n {/* Pagination */}\n {totalCount > pagination.pageSize && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-3 pt-2\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"export.history.pagination.showing\",\n \"Showing {{start}}-{{end}} of {{total}}\",\n {\n start: pageStart,\n end: pageEnd,\n total: totalCount,\n },\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrevPage}\n disabled={!hasPrevPage}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n <ChevronLeft size={16} />\n {tr(\"export.history.pagination.prev\", \"Prev\")}\n </button>\n <span className=\"text-sm text-text-secondary px-2\">\n {tr(\"export.history.pagination.page\", \"Page {{page}}\", {\n page: pagination.page,\n })}\n </span>\n <button\n type=\"button\"\n onClick={handleNextPage}\n disabled={!hasNextPage}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {tr(\"export.history.pagination.next\", \"Next\")}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n )}\n </>\n ) : (\n <IllustratedEmptyState\n illustration={hasActiveFilters ? \"empty-search\" : \"empty-data\"}\n title={\n hasActiveFilters\n ? tr(\n \"export.history.empty.filtered.title\",\n \"No matching exports\",\n )\n : tr(\"export.history.empty.title\", \"No exports yet\")\n }\n description={\n hasActiveFilters\n ? tr(\n \"export.history.empty.filtered.description\",\n \"Try adjusting your search or filters.\",\n )\n : tr(\n \"export.history.empty.description\",\n \"Create your first export to see it here.\",\n )\n }\n action={\n hasActiveFilters ? (\n <button\n type=\"button\"\n onClick={handleResetFilters}\n className=\"inline-flex items-center gap-2 px-4 py-2 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium\"\n >\n <X size={16} />\n {tr(\n \"export.history.empty.filtered.actions.clear\",\n \"Clear Filters\",\n )}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigate?.(joinPath(basePath, \"new\"))}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Download size={16} />\n {tr(\n \"export.history.empty.actions.createFirst\",\n \"Create First Export\",\n )}\n </button>\n )\n }\n />\n ))}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportHistoryPage.js","names":[],"sources":["../../../src/export/pages/ExportHistoryPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport {\n Search,\n X,\n Clock,\n Download,\n ChevronRight,\n ChevronLeft,\n RefreshCw,\n AlertCircle,\n Filter,\n} from \"lucide-react\";\nimport { IllustratedEmptyState, PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport { useExportJobs } from \"../hooks/useExportQueries\";\nimport { useExportStore } from \"../store/exportStore\";\nimport { ExportStatusBadge } from \"../components/ExportStatusBadge\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { useTr } from \"../hooks/useTr\";\nimport { cn } from \"../utils/cn\";\nimport {\n getFormatDisplayName,\n getAllStatuses,\n getStatusDisplayName,\n} from \"../constants/enums\";\nimport type { ExportJobStatus, ExportFormat } from \"../types\";\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 History Page\n * Table/list of all past exports with filters and cursor-based pagination\n */\nexport function ExportHistoryPage() {\n const { basePath = \"/\", navigate } = useExport();\n const tr = useTr();\n const { jobFilter, updateFilter, resetFilter, pagination, setPagination } =\n useExportStore();\n const [searchParams] = useSearchParams();\n\n // Initialize status filter from URL search params (e.g. ?status=RUNNING) — once\n const didInitFromUrlRef = useRef(false);\n useEffect(() => {\n if (didInitFromUrlRef.current) return;\n didInitFromUrlRef.current = true;\n const statusParam = searchParams.get(\"status\");\n if (statusParam) {\n updateFilter({\n status: statusParam as ExportJobStatus,\n });\n }\n }, [searchParams, updateFilter]);\n\n const [showFilters, setShowFilters] = useState(false);\n // Cursor stack: index 0 = first page (no cursor), subsequent entries are \"after\" cursors\n const [cursorStack, setCursorStack] = useState<(string | null)[]>([null]);\n const [currentPageIndex, setCurrentPageIndex] = useState(0);\n\n const currentCursor = cursorStack[currentPageIndex] ?? null;\n\n const {\n data: jobs,\n totalCount,\n pageInfo,\n isLoading,\n error,\n refetch,\n } = useExportJobs(jobFilter, {\n first: pagination.pageSize,\n after: currentCursor ?? undefined,\n });\n\n // Client-side search filter (server-side filtering handles status/format)\n const filteredJobs = useMemo(() => {\n if (!jobFilter.search) return jobs;\n const q = jobFilter.search.toLowerCase();\n return jobs.filter(\n (job) =>\n job.name.toLowerCase().includes(q) ||\n job.entityTypes.some((e) => e.toLowerCase().includes(q)),\n );\n }, [jobs, jobFilter.search]);\n\n const hasActiveFilters =\n jobFilter.search ||\n jobFilter.status !== \"all\" ||\n jobFilter.format !== \"all\";\n\n const handleJobClick = (jobId: string) => {\n navigate?.(joinPath(basePath, jobId));\n };\n\n const handleNextPage = () => {\n if (!pageInfo?.hasNextPage || !pageInfo.endCursor) return;\n const newStack = [\n ...cursorStack.slice(0, currentPageIndex + 1),\n pageInfo.endCursor,\n ];\n setCursorStack(newStack);\n setCurrentPageIndex((prev) => prev + 1);\n setPagination({ page: pagination.page + 1, cursor: pageInfo.endCursor });\n };\n\n const handlePrevPage = () => {\n if (currentPageIndex === 0) return;\n const prevIndex = currentPageIndex - 1;\n setCurrentPageIndex(prevIndex);\n setPagination({\n page: pagination.page - 1,\n cursor: cursorStack[prevIndex] ?? null,\n });\n };\n\n const handleResetFilters = () => {\n resetFilter();\n setCursorStack([null]);\n setCurrentPageIndex(0);\n };\n\n const hasPrevPage = currentPageIndex > 0;\n const hasNextPage = pageInfo?.hasNextPage ?? false;\n const pageStart = currentPageIndex * pagination.pageSize + 1;\n const pageEnd = Math.min(\n (currentPageIndex + 1) * pagination.pageSize,\n totalCount,\n );\n const getStatusLabel = (status: ExportJobStatus) =>\n tr(\n `export.shared.status.${status.toLowerCase()}`,\n getStatusDisplayName(status),\n );\n\n return (\n <PageLayout\n title={tr(\"export.history.title\", \"Export History\")}\n subtitle={tr(\n \"export.history.subtitle\",\n \"View and manage all your past export jobs\",\n )}\n icon={<Clock size={24} />}\n headerActions={\n <button\n type=\"button\"\n onClick={() => navigate?.(joinPath(basePath, \"new\"))}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Download size={16} />\n {tr(\"export.history.actions.newExport\", \"New Export\")}\n </button>\n }\n >\n {/* Page purpose — what History is for */}\n <PagePurpose icon={<Clock size={16} />}>\n {tr(\n \"export.history.purpose\",\n \"Every export you've ever started lives here. Track jobs as they run, see which finished or failed, and grab download links for completed exports before they expire. Search by name or entity type, or filter by status and format to find a specific one.\",\n )}\n </PagePurpose>\n\n {/* Search & Filters */}\n <div className=\"space-y-4\">\n <div className=\"flex flex-col sm:flex-row gap-3\">\n {/* Search */}\n <div className=\"flex-1 relative group\">\n <Search\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors\"\n />\n <input\n type=\"text\"\n aria-label={tr(\n \"export.history.search.placeholder\",\n \"Search exports by name or entity type\",\n )}\n placeholder={tr(\n \"export.history.search.placeholder\",\n \"Search exports by name or entity type...\",\n )}\n value={jobFilter.search}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-[3.25rem] pr-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n />\n </div>\n\n {/* Filter Toggle */}\n <button\n type=\"button\"\n onClick={() => setShowFilters(!showFilters)}\n className={cn(\n \"inline-flex items-center gap-2 px-4 py-2.5 border-2 rounded-lg transition-all duration-200 font-medium\",\n showFilters || hasActiveFilters\n ? \"bg-action-primary-bg text-action-primary-text border-primary\"\n : \"border-border-subtle/50 hover:border-primary/30\",\n )}\n >\n <Filter size={16} />\n {showFilters\n ? tr(\"export.history.filters.hide\", \"Hide\")\n : tr(\"export.history.filters.show\", \"Filter\")}\n </button>\n </div>\n\n {/* Filter Panel */}\n {showFilters && (\n <div className=\"border border-border-seam rounded-xl p-4 animate-in fade-in-0 duration-200\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"font-semibold flex items-center gap-2\">\n <Filter size={16} className=\"text-primary\" />\n {tr(\"export.history.filters.title\", \"Filters\")}\n </h3>\n {hasActiveFilters && (\n <button\n type=\"button\"\n onClick={handleResetFilters}\n className=\"text-sm text-text-secondary hover:text-status-error-text flex items-center gap-1\"\n >\n <X size={14} />\n {tr(\"export.history.filters.clearAll\", \"Clear All\")}\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n {/* Status Filter */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium\">\n {tr(\"export.history.filters.status.label\", \"Status\")}\n </label>\n <select\n value={jobFilter.status}\n onChange={(e) =>\n updateFilter({\n status: e.target.value as ExportJobStatus | \"all\",\n })\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"all\">\n {tr(\"export.history.filters.status.all\", \"All Statuses\")}\n </option>\n {getAllStatuses().map((status) => (\n <option key={status} value={status}>\n {getStatusLabel(status)}\n </option>\n ))}\n </select>\n </div>\n\n {/* Format Filter — only CSV and JSON are supported */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium\">\n {tr(\"export.history.filters.format.label\", \"Format\")}\n </label>\n <select\n value={jobFilter.format}\n onChange={(e) =>\n updateFilter({\n format: e.target.value as ExportFormat | \"all\",\n })\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"all\">\n {tr(\"export.history.filters.format.all\", \"All Formats\")}\n </option>\n <option value=\"CSV\">{tr(\"export.format.csv\", \"CSV\")}</option>\n <option value=\"JSON\">\n {tr(\"export.format.json\", \"JSON\")}\n </option>\n </select>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Error State */}\n {error && (\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\"\n />\n <div>\n <h3 className=\"font-semibold text-status-error-text mb-1\">\n {tr(\"export.history.error.title\", \"Error loading exports\")}\n </h3>\n <p className=\"text-sm text-status-error-text mb-3\">\n {error.message}\n </p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors\"\n >\n <RefreshCw size={14} />\n {tr(\"export.history.error.retry\", \"Try Again\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Loading */}\n {isLoading && (\n <div className=\"space-y-3\">\n {[...Array(5)].map((_, i) => (\n <div key={i} className=\"border border-border-seam rounded-xl p-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"space-y-2\">\n <div className=\"h-4 w-48 bg-bg-sunken rounded animate-vc-shimmer\" />\n <div className=\"h-3 w-32 bg-bg-sunken rounded animate-vc-shimmer\" />\n </div>\n <div className=\"h-5 w-20 bg-bg-sunken rounded-full animate-vc-shimmer\" />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Jobs List */}\n {!isLoading &&\n !error &&\n (filteredJobs.length > 0 ? (\n <>\n <div className=\"space-y-2\">\n {filteredJobs.map((job, index) => (\n <button\n type=\"button\"\n key={job.id}\n onClick={() => handleJobClick(job.id)}\n className={cn(\n \"w-full text-left border border-border-seam rounded-xl p-4 bg-bg-surface\",\n \"hover:border-primary/40 transition-colors duration-200\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\",\n \"group animate-in fade-in-0\",\n )}\n style={{ animationDelay: `${index * 30}ms` }}\n >\n <div className=\"flex items-start sm:items-center justify-between gap-3\">\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-semibold text-text-primary truncate\">\n {job.name}\n </h4>\n <p className=\"text-sm text-text-secondary mt-1 line-clamp-2\">\n {job.entityTypes.join(\", \")} ·{\" \"}\n {getFormatDisplayName(job.format)}\n {job.createdAt &&\n ` · ${new Date(job.createdAt).toLocaleDateString()}`}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <ExportStatusBadge status={job.status} />\n <ChevronRight\n size={18}\n className=\"text-text-secondary group-hover:text-primary transition-colors hidden sm:block\"\n />\n </div>\n </div>\n </button>\n ))}\n </div>\n\n {/* Pagination */}\n {totalCount > pagination.pageSize && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-3 pt-2\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"export.history.pagination.showing\",\n \"Showing {{start}}-{{end}} of {{total}}\",\n {\n start: pageStart,\n end: pageEnd,\n total: totalCount,\n },\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrevPage}\n disabled={!hasPrevPage}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n <ChevronLeft size={16} />\n {tr(\"export.history.pagination.prev\", \"Prev\")}\n </button>\n <span className=\"text-sm text-text-secondary px-2\">\n {tr(\"export.history.pagination.page\", \"Page {{page}}\", {\n page: pagination.page,\n })}\n </span>\n <button\n type=\"button\"\n onClick={handleNextPage}\n disabled={!hasNextPage}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border-2 border-border-subtle/50 rounded-lg hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {tr(\"export.history.pagination.next\", \"Next\")}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n )}\n </>\n ) : (\n <IllustratedEmptyState\n illustration={hasActiveFilters ? \"empty-search\" : \"empty-data\"}\n title={\n hasActiveFilters\n ? tr(\n \"export.history.empty.filtered.title\",\n \"No matching exports\",\n )\n : tr(\"export.history.empty.title\", \"No exports yet\")\n }\n description={\n hasActiveFilters\n ? tr(\n \"export.history.empty.filtered.description\",\n \"Try adjusting your search or filters.\",\n )\n : tr(\n \"export.history.empty.description\",\n \"Create your first export to see it here.\",\n )\n }\n action={\n hasActiveFilters ? (\n <button\n type=\"button\"\n onClick={handleResetFilters}\n className=\"inline-flex items-center gap-2 px-4 py-2 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium\"\n >\n <X size={16} />\n {tr(\n \"export.history.empty.filtered.actions.clear\",\n \"Clear Filters\",\n )}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigate?.(joinPath(basePath, \"new\"))}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Download size={16} />\n {tr(\n \"export.history.empty.actions.createFirst\",\n \"Create First Export\",\n )}\n </button>\n )\n }\n />\n ))}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -124,7 +124,7 @@ function w() {
|
|
|
124
124
|
}), /* @__PURE__ */ y("select", {
|
|
125
125
|
value: U,
|
|
126
126
|
onChange: (e) => W(e.target.value),
|
|
127
|
-
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all",
|
|
127
|
+
className: "w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
128
128
|
children: [/* @__PURE__ */ v("option", {
|
|
129
129
|
value: "CSV",
|
|
130
130
|
children: E("export.format.csv", "CSV")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportTemplatesPage.js","names":[],"sources":["../../../src/export/pages/ExportTemplatesPage.tsx"],"sourcesContent":["import { useState, useMemo } from \"react\";\nimport {\n Search,\n X,\n FileBox,\n Plus,\n ChevronRight,\n Sparkles,\n Loader2,\n} from \"lucide-react\";\nimport {\n CTAOverflowMenu,\n IllustratedEmptyState,\n PagePurpose,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useExportTemplates,\n useDeleteExportTemplate,\n useCreateExportTemplate,\n useAvailableEntities,\n} from \"../hooks/useExportQueries\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { getFormatDisplayName } from \"../constants/enums\";\nimport { useTr } from \"../hooks/useTr\";\nimport { cn } from \"../utils/cn\";\nimport type { ExportFormat } from \"../types\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\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 Templates Page\n * Template management list for reusable export configurations\n */\nexport function ExportTemplatesPage() {\n const { basePath = \"/\", navigate } = useExport();\n const tr = useTr();\n const { data: templates, isLoading } = useExportTemplates();\n const { deleteTemplate } = useDeleteExportTemplate();\n const { createTemplate, isPending: creating } = useCreateExportTemplate();\n const { data: availableEntities } = useAvailableEntities();\n\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [newTemplateName, setNewTemplateName] = useState(\"\");\n const [newTemplateDescription, setNewTemplateDescription] = useState(\"\");\n const [newTemplateEntityTypes, setNewTemplateEntityTypes] = useState<\n string[]\n >([]);\n const [newTemplateFormat, setNewTemplateFormat] =\n useState<ExportFormat>(\"CSV\");\n const [createError, setCreateError] = useState<string | null>(null);\n const [templatePendingDelete, setTemplatePendingDelete] = useState<\n string | null\n >(null);\n\n const handleOpenCreate = () => {\n setNewTemplateName(\"\");\n setNewTemplateDescription(\"\");\n setNewTemplateEntityTypes([]);\n setNewTemplateFormat(\"CSV\");\n setCreateError(null);\n setShowCreateForm(true);\n };\n\n const handleCreateTemplate = async () => {\n if (!newTemplateName.trim()) {\n setCreateError(\n tr(\n \"export.templates.form.errors.nameRequired\",\n \"Template name is required.\",\n ),\n );\n return;\n }\n if (newTemplateEntityTypes.length === 0) {\n setCreateError(\n tr(\n \"export.templates.form.errors.entityRequired\",\n \"Select at least one entity type.\",\n ),\n );\n return;\n }\n setCreateError(null);\n try {\n await createTemplate({\n name: newTemplateName.trim(),\n description: newTemplateDescription.trim() || undefined,\n entityTypes: newTemplateEntityTypes,\n format: newTemplateFormat,\n });\n setShowCreateForm(false);\n } catch (err) {\n setCreateError(\n err instanceof Error\n ? err.message\n : tr(\n \"export.templates.form.errors.createFailed\",\n \"Failed to create template.\",\n ),\n );\n }\n };\n\n const toggleEntity = (entityType: string) => {\n setNewTemplateEntityTypes((prev) =>\n prev.includes(entityType)\n ? prev.filter((e) => e !== entityType)\n : [...prev, entityType],\n );\n };\n\n // Client-side search filter\n const filteredTemplates = useMemo(() => {\n if (!searchQuery) return templates;\n const q = searchQuery.toLowerCase();\n return templates.filter(\n (t) =>\n t.name.toLowerCase().includes(q) ||\n t.entityTypes.some((e) => e.toLowerCase().includes(q)),\n );\n }, [templates, searchQuery]);\n\n const handleTemplateClick = (templateId: string) => {\n navigate?.(joinPath(basePath, `templates/${templateId}`));\n };\n\n const handleDeleteTemplate = async (\n e: React.MouseEvent,\n templateId: string,\n ) => {\n e.stopPropagation();\n nativeImpact(\"medium\");\n setTemplatePendingDelete(templateId);\n };\n\n const confirmDeleteTemplate = async () => {\n if (!templatePendingDelete) return;\n try {\n await deleteTemplate(templatePendingDelete);\n nativeNotify(\"success\");\n } catch {\n nativeNotify(\"error\");\n }\n setTemplatePendingDelete(null);\n };\n\n return (\n <PageLayout\n title={tr(\"export.templates.title\", \"Export Templates\")}\n subtitle={tr(\n \"export.templates.subtitle\",\n \"Reusable export configurations for common data exports\",\n )}\n icon={<FileBox size={24} />}\n headerActions={\n <CTAOverflowMenu\n primary={{\n label: tr(\"export.templates.actions.newTemplate\", \"New Template\"),\n icon: <Plus size={16} />,\n onSelect: handleOpenCreate,\n }}\n actions={[\n {\n label: tr(\"export.templates.actions.newExport\", \"New Export\"),\n icon: <Plus size={16} />,\n onSelect: () => navigate?.(joinPath(basePath, \"new\")),\n },\n ]}\n />\n }\n headerContent={\n <span className=\"inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-bg-sunken text-text-secondary\">\n <Sparkles size={12} />\n {tr(\n \"export.templates.summary.available\",\n \"{{count}} template{{suffix}} available\",\n {\n count: filteredTemplates.length,\n suffix: filteredTemplates.length !== 1 ? \"s\" : \"\",\n },\n )}\n </span>\n }\n >\n {/* Page purpose — why templates exist */}\n <PagePurpose icon={<FileBox size={16} />}>\n {tr(\n \"export.templates.purpose\",\n \"Templates save an export setup — the entity types and format you regularly need — so you don't rebuild it each time. Create one here, then start a new export from it in a single click. Handy for recurring backups, periodic audits, or any export you run more than once.\",\n )}\n </PagePurpose>\n\n {/* Create Template Form */}\n {showCreateForm && (\n <div className=\"border border-primary/30 rounded-xl p-6 bg-accent-wash space-y-4\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"font-semibold text-lg\">\n {tr(\"export.templates.form.title\", \"New Template\")}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n className=\"p-1.5 rounded-md hover:bg-accent\"\n >\n <X size={16} />\n </button>\n </div>\n {createError && (\n <p className=\"text-sm text-status-error-text bg-status-error-bg/10 rounded-lg px-3 py-2\">\n {createError}\n </p>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div className=\"space-y-1\">\n <label className=\"text-sm font-medium\">\n {tr(\"export.templates.form.fields.name\", \"Name\")} *\n </label>\n <input\n type=\"text\"\n aria-label={tr(\"export.templates.form.fields.name\", \"Name\")}\n placeholder={tr(\n \"export.templates.form.fields.namePlaceholder\",\n \"e.g. Weekly Tags Export\",\n )}\n value={newTemplateName}\n onChange={(e) => setNewTemplateName(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all\"\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-sm font-medium\">\n {tr(\"export.templates.form.fields.format\", \"Format\")} *\n </label>\n <select\n value={newTemplateFormat}\n onChange={(e) =>\n setNewTemplateFormat(e.target.value as ExportFormat)\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all\"\n >\n <option value=\"CSV\">{tr(\"export.format.csv\", \"CSV\")}</option>\n <option value=\"JSON\">{tr(\"export.format.json\", \"JSON\")}</option>\n </select>\n </div>\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-sm font-medium\">\n {tr(\"export.templates.form.fields.description\", \"Description\")}\n </label>\n <input\n type=\"text\"\n aria-label={tr(\n \"export.templates.form.fields.description\",\n \"Description\",\n )}\n placeholder={tr(\n \"export.templates.form.fields.descriptionPlaceholder\",\n \"Optional description\",\n )}\n value={newTemplateDescription}\n onChange={(e) => setNewTemplateDescription(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all\"\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr(\"export.templates.form.fields.entityTypes\", \"Entity Types\")} *{\" \"}\n <span className=\"text-text-secondary font-normal\">\n {tr(\n \"export.templates.form.fields.entityTypesHint\",\n \"(select at least one)\",\n )}\n </span>\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {availableEntities.map((entity) => (\n <button\n key={entity.entityType}\n type=\"button\"\n onClick={() => toggleEntity(entity.entityType)}\n className={cn(\n \"px-3 py-1.5 rounded-lg text-sm font-medium border-2 transition-all\",\n newTemplateEntityTypes.includes(entity.entityType)\n ? \"border-primary bg-action-primary-bg/10 text-primary\"\n : \"border-border-subtle bg-bg-surface hover:border-primary/50\",\n )}\n >\n {entity.displayName}\n </button>\n ))}\n </div>\n </div>\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={handleCreateTemplate}\n disabled={creating}\n className=\"inline-flex items-center gap-2 px-5 py-2.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium disabled:opacity-50\"\n >\n {creating ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Plus size={16} />\n )}\n {tr(\"export.templates.form.actions.create\", \"Create Template\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n className=\"px-5 py-2.5 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium\"\n >\n {tr(\"export.templates.form.actions.cancel\", \"Cancel\")}\n </button>\n </div>\n </div>\n )}\n {/* Search */}\n <div className=\"relative group\">\n <Search\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors\"\n />\n <input\n type=\"text\"\n aria-label={tr(\n \"export.templates.search.placeholder\",\n \"Search templates\",\n )}\n placeholder={tr(\n \"export.templates.search.placeholder\",\n \"Search templates...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n />\n {searchQuery && (\n <button\n type=\"button\"\n onClick={() => setSearchQuery(\"\")}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-md text-text-secondary hover:text-text-primary hover:bg-accent\"\n >\n <X size={16} />\n </button>\n )}\n </div>\n\n {/* Loading */}\n {isLoading && (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-4\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"border border-border-seam rounded-xl p-5\">\n <div className=\"h-5 w-40 bg-bg-sunken rounded mb-3 animate-vc-shimmer\" />\n <div className=\"h-3 w-full bg-bg-sunken rounded mb-2 animate-vc-shimmer\" />\n <div className=\"flex gap-2 mt-4\">\n <div className=\"h-5 w-16 bg-bg-sunken rounded-full animate-vc-shimmer\" />\n <div className=\"h-5 w-16 bg-bg-sunken rounded-full animate-vc-shimmer\" />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Templates Grid */}\n {!isLoading ? (\n filteredTemplates.length > 0 ? (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-4\">\n {filteredTemplates.map((template, index) => (\n <div\n key={template.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => handleTemplateClick(template.id)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleTemplateClick(template.id);\n }\n }}\n className={cn(\n \"text-left group cursor-pointer border border-border-seam rounded-xl overflow-hidden bg-bg-surface\",\n \"hover:border-primary/40 shadow-[var(--shadow-pop)] transition-colors duration-200\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\",\n \"animate-in fade-in-0\",\n )}\n style={{ animationDelay: `${index * 50}ms` }}\n >\n {/* Header */}\n <div className=\"p-4 border-b bg-bg-sunken/30\">\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-lg bg-action-primary-bg/10\">\n <FileBox size={20} className=\"text-primary\" />\n </div>\n <div>\n <h3 className=\"font-semibold\">{template.name}</h3>\n <p className=\"text-xs text-text-secondary\">\n {getFormatDisplayName(template.format)}\n {template.isDefault && (\n <span className=\"ml-1.5 inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-action-primary-bg/10 text-primary font-medium\">\n {tr(\"export.templates.card.default\", \"Default\")}\n </span>\n )}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={(e) => handleDeleteTemplate(e, template.id)}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-status-error-text hover:bg-status-error-bg/10 transition-colors\"\n aria-label={tr(\n \"export.templates.card.deleteAriaLabel\",\n \"Delete {name}\",\n { name: template.name },\n )}\n >\n <X size={14} />\n </button>\n <ChevronRight\n size={18}\n className=\"text-text-secondary group-hover:text-primary transition-colors mt-1\"\n />\n </div>\n </div>\n </div>\n\n {/* Body */}\n <div className=\"p-4\">\n {template.description && (\n <p className=\"text-sm text-text-secondary mb-3 line-clamp-2\">\n {template.description}\n </p>\n )}\n <div className=\"flex flex-wrap gap-1.5 overflow-hidden\">\n {template.entityTypes.slice(0, 6).map((entity) => (\n <span\n key={entity}\n className=\"inline-flex px-2 py-0.5 rounded text-xs font-medium bg-bg-sunken text-text-secondary max-w-full truncate\"\n >\n {entity}\n </span>\n ))}\n {template.entityTypes.length > 6 && (\n <span className=\"inline-flex px-2 py-0.5 rounded text-xs font-medium bg-bg-sunken text-text-secondary\">\n {tr(\n \"export.templates.card.moreEntities\",\n \"+{count} more\",\n {\n count: template.entityTypes.length - 6,\n },\n )}\n </span>\n )}\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <IllustratedEmptyState\n illustration={searchQuery ? \"empty-search\" : \"empty-data\"}\n title={\n searchQuery\n ? tr(\n \"export.templates.empty.filtered.title\",\n \"No matching templates\",\n )\n : tr(\"export.templates.empty.title\", \"No templates yet\")\n }\n description={\n searchQuery\n ? tr(\n \"export.templates.empty.filtered.description\",\n \"Try adjusting your search to find what you are looking for.\",\n )\n : tr(\n \"export.templates.empty.description\",\n \"Create a template to save reusable export configurations.\",\n )\n }\n action={\n searchQuery ? (\n <button\n type=\"button\"\n onClick={() => setSearchQuery(\"\")}\n className=\"inline-flex items-center gap-2 px-4 py-2 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium\"\n >\n <X size={16} />\n {tr(\n \"export.templates.empty.filtered.actions.clear\",\n \"Clear Search\",\n )}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={handleOpenCreate}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Plus size={16} />\n {tr(\n \"export.templates.empty.actions.newTemplate\",\n \"New Template\",\n )}\n </button>\n )\n }\n />\n )\n ) : null}\n {templatePendingDelete && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"template-list-delete-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=\"template-list-delete-title\" className=\"text-lg font-bold\">\n {tr(\"export.templates.delete.title\", \"Delete template?\")}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {tr(\n \"export.templates.delete.description\",\n \"This removes the saved export configuration. Existing export jobs are not deleted.\",\n )}\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={() => setTemplatePendingDelete(null)}\n className=\"px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium\"\n >\n {tr(\"export.templates.delete.cancel\", \"Keep Template\")}\n </button>\n <button\n type=\"button\"\n onClick={confirmDeleteTemplate}\n className=\"px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium\"\n >\n {tr(\"export.templates.delete.confirmAction\", \"Delete Template\")}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportTemplatesPage.js","names":[],"sources":["../../../src/export/pages/ExportTemplatesPage.tsx"],"sourcesContent":["import { useState, useMemo } from \"react\";\nimport {\n Search,\n X,\n FileBox,\n Plus,\n ChevronRight,\n Sparkles,\n Loader2,\n} from \"lucide-react\";\nimport {\n CTAOverflowMenu,\n IllustratedEmptyState,\n PagePurpose,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useExport } from \"../providers/ExportProvider\";\nimport {\n useExportTemplates,\n useDeleteExportTemplate,\n useCreateExportTemplate,\n useAvailableEntities,\n} from \"../hooks/useExportQueries\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { getFormatDisplayName } from \"../constants/enums\";\nimport { useTr } from \"../hooks/useTr\";\nimport { cn } from \"../utils/cn\";\nimport type { ExportFormat } from \"../types\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\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 Templates Page\n * Template management list for reusable export configurations\n */\nexport function ExportTemplatesPage() {\n const { basePath = \"/\", navigate } = useExport();\n const tr = useTr();\n const { data: templates, isLoading } = useExportTemplates();\n const { deleteTemplate } = useDeleteExportTemplate();\n const { createTemplate, isPending: creating } = useCreateExportTemplate();\n const { data: availableEntities } = useAvailableEntities();\n\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [newTemplateName, setNewTemplateName] = useState(\"\");\n const [newTemplateDescription, setNewTemplateDescription] = useState(\"\");\n const [newTemplateEntityTypes, setNewTemplateEntityTypes] = useState<\n string[]\n >([]);\n const [newTemplateFormat, setNewTemplateFormat] =\n useState<ExportFormat>(\"CSV\");\n const [createError, setCreateError] = useState<string | null>(null);\n const [templatePendingDelete, setTemplatePendingDelete] = useState<\n string | null\n >(null);\n\n const handleOpenCreate = () => {\n setNewTemplateName(\"\");\n setNewTemplateDescription(\"\");\n setNewTemplateEntityTypes([]);\n setNewTemplateFormat(\"CSV\");\n setCreateError(null);\n setShowCreateForm(true);\n };\n\n const handleCreateTemplate = async () => {\n if (!newTemplateName.trim()) {\n setCreateError(\n tr(\n \"export.templates.form.errors.nameRequired\",\n \"Template name is required.\",\n ),\n );\n return;\n }\n if (newTemplateEntityTypes.length === 0) {\n setCreateError(\n tr(\n \"export.templates.form.errors.entityRequired\",\n \"Select at least one entity type.\",\n ),\n );\n return;\n }\n setCreateError(null);\n try {\n await createTemplate({\n name: newTemplateName.trim(),\n description: newTemplateDescription.trim() || undefined,\n entityTypes: newTemplateEntityTypes,\n format: newTemplateFormat,\n });\n setShowCreateForm(false);\n } catch (err) {\n setCreateError(\n err instanceof Error\n ? err.message\n : tr(\n \"export.templates.form.errors.createFailed\",\n \"Failed to create template.\",\n ),\n );\n }\n };\n\n const toggleEntity = (entityType: string) => {\n setNewTemplateEntityTypes((prev) =>\n prev.includes(entityType)\n ? prev.filter((e) => e !== entityType)\n : [...prev, entityType],\n );\n };\n\n // Client-side search filter\n const filteredTemplates = useMemo(() => {\n if (!searchQuery) return templates;\n const q = searchQuery.toLowerCase();\n return templates.filter(\n (t) =>\n t.name.toLowerCase().includes(q) ||\n t.entityTypes.some((e) => e.toLowerCase().includes(q)),\n );\n }, [templates, searchQuery]);\n\n const handleTemplateClick = (templateId: string) => {\n navigate?.(joinPath(basePath, `templates/${templateId}`));\n };\n\n const handleDeleteTemplate = async (\n e: React.MouseEvent,\n templateId: string,\n ) => {\n e.stopPropagation();\n nativeImpact(\"medium\");\n setTemplatePendingDelete(templateId);\n };\n\n const confirmDeleteTemplate = async () => {\n if (!templatePendingDelete) return;\n try {\n await deleteTemplate(templatePendingDelete);\n nativeNotify(\"success\");\n } catch {\n nativeNotify(\"error\");\n }\n setTemplatePendingDelete(null);\n };\n\n return (\n <PageLayout\n title={tr(\"export.templates.title\", \"Export Templates\")}\n subtitle={tr(\n \"export.templates.subtitle\",\n \"Reusable export configurations for common data exports\",\n )}\n icon={<FileBox size={24} />}\n headerActions={\n <CTAOverflowMenu\n primary={{\n label: tr(\"export.templates.actions.newTemplate\", \"New Template\"),\n icon: <Plus size={16} />,\n onSelect: handleOpenCreate,\n }}\n actions={[\n {\n label: tr(\"export.templates.actions.newExport\", \"New Export\"),\n icon: <Plus size={16} />,\n onSelect: () => navigate?.(joinPath(basePath, \"new\")),\n },\n ]}\n />\n }\n headerContent={\n <span className=\"inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-bg-sunken text-text-secondary\">\n <Sparkles size={12} />\n {tr(\n \"export.templates.summary.available\",\n \"{{count}} template{{suffix}} available\",\n {\n count: filteredTemplates.length,\n suffix: filteredTemplates.length !== 1 ? \"s\" : \"\",\n },\n )}\n </span>\n }\n >\n {/* Page purpose — why templates exist */}\n <PagePurpose icon={<FileBox size={16} />}>\n {tr(\n \"export.templates.purpose\",\n \"Templates save an export setup — the entity types and format you regularly need — so you don't rebuild it each time. Create one here, then start a new export from it in a single click. Handy for recurring backups, periodic audits, or any export you run more than once.\",\n )}\n </PagePurpose>\n\n {/* Create Template Form */}\n {showCreateForm && (\n <div className=\"border border-primary/30 rounded-xl p-6 bg-accent-wash space-y-4\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"font-semibold text-lg\">\n {tr(\"export.templates.form.title\", \"New Template\")}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n className=\"p-1.5 rounded-md hover:bg-accent\"\n >\n <X size={16} />\n </button>\n </div>\n {createError && (\n <p className=\"text-sm text-status-error-text bg-status-error-bg/10 rounded-lg px-3 py-2\">\n {createError}\n </p>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div className=\"space-y-1\">\n <label className=\"text-sm font-medium\">\n {tr(\"export.templates.form.fields.name\", \"Name\")} *\n </label>\n <input\n type=\"text\"\n aria-label={tr(\"export.templates.form.fields.name\", \"Name\")}\n placeholder={tr(\n \"export.templates.form.fields.namePlaceholder\",\n \"e.g. Weekly Tags Export\",\n )}\n value={newTemplateName}\n onChange={(e) => setNewTemplateName(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all\"\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-sm font-medium\">\n {tr(\"export.templates.form.fields.format\", \"Format\")} *\n </label>\n <select\n value={newTemplateFormat}\n onChange={(e) =>\n setNewTemplateFormat(e.target.value as ExportFormat)\n }\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"CSV\">{tr(\"export.format.csv\", \"CSV\")}</option>\n <option value=\"JSON\">{tr(\"export.format.json\", \"JSON\")}</option>\n </select>\n </div>\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-sm font-medium\">\n {tr(\"export.templates.form.fields.description\", \"Description\")}\n </label>\n <input\n type=\"text\"\n aria-label={tr(\n \"export.templates.form.fields.description\",\n \"Description\",\n )}\n placeholder={tr(\n \"export.templates.form.fields.descriptionPlaceholder\",\n \"Optional description\",\n )}\n value={newTemplateDescription}\n onChange={(e) => setNewTemplateDescription(e.target.value)}\n className=\"w-full px-3 py-2 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all\"\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr(\"export.templates.form.fields.entityTypes\", \"Entity Types\")} *{\" \"}\n <span className=\"text-text-secondary font-normal\">\n {tr(\n \"export.templates.form.fields.entityTypesHint\",\n \"(select at least one)\",\n )}\n </span>\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {availableEntities.map((entity) => (\n <button\n key={entity.entityType}\n type=\"button\"\n onClick={() => toggleEntity(entity.entityType)}\n className={cn(\n \"px-3 py-1.5 rounded-lg text-sm font-medium border-2 transition-all\",\n newTemplateEntityTypes.includes(entity.entityType)\n ? \"border-primary bg-action-primary-bg/10 text-primary\"\n : \"border-border-subtle bg-bg-surface hover:border-primary/50\",\n )}\n >\n {entity.displayName}\n </button>\n ))}\n </div>\n </div>\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={handleCreateTemplate}\n disabled={creating}\n className=\"inline-flex items-center gap-2 px-5 py-2.5 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium disabled:opacity-50\"\n >\n {creating ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Plus size={16} />\n )}\n {tr(\"export.templates.form.actions.create\", \"Create Template\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n className=\"px-5 py-2.5 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium\"\n >\n {tr(\"export.templates.form.actions.cancel\", \"Cancel\")}\n </button>\n </div>\n </div>\n )}\n {/* Search */}\n <div className=\"relative group\">\n <Search\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary group-focus-within:text-primary transition-colors\"\n />\n <input\n type=\"text\"\n aria-label={tr(\n \"export.templates.search.placeholder\",\n \"Search templates\",\n )}\n placeholder={tr(\n \"export.templates.search.placeholder\",\n \"Search templates...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2.5 border-2 border-border-subtle rounded-lg bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all duration-200\"\n />\n {searchQuery && (\n <button\n type=\"button\"\n onClick={() => setSearchQuery(\"\")}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-md text-text-secondary hover:text-text-primary hover:bg-accent\"\n >\n <X size={16} />\n </button>\n )}\n </div>\n\n {/* Loading */}\n {isLoading && (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-4\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"border border-border-seam rounded-xl p-5\">\n <div className=\"h-5 w-40 bg-bg-sunken rounded mb-3 animate-vc-shimmer\" />\n <div className=\"h-3 w-full bg-bg-sunken rounded mb-2 animate-vc-shimmer\" />\n <div className=\"flex gap-2 mt-4\">\n <div className=\"h-5 w-16 bg-bg-sunken rounded-full animate-vc-shimmer\" />\n <div className=\"h-5 w-16 bg-bg-sunken rounded-full animate-vc-shimmer\" />\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Templates Grid */}\n {!isLoading ? (\n filteredTemplates.length > 0 ? (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-4\">\n {filteredTemplates.map((template, index) => (\n <div\n key={template.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => handleTemplateClick(template.id)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleTemplateClick(template.id);\n }\n }}\n className={cn(\n \"text-left group cursor-pointer border border-border-seam rounded-xl overflow-hidden bg-bg-surface\",\n \"hover:border-primary/40 shadow-[var(--shadow-pop)] transition-colors duration-200\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\",\n \"animate-in fade-in-0\",\n )}\n style={{ animationDelay: `${index * 50}ms` }}\n >\n {/* Header */}\n <div className=\"p-4 border-b bg-bg-sunken/30\">\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-lg bg-action-primary-bg/10\">\n <FileBox size={20} className=\"text-primary\" />\n </div>\n <div>\n <h3 className=\"font-semibold\">{template.name}</h3>\n <p className=\"text-xs text-text-secondary\">\n {getFormatDisplayName(template.format)}\n {template.isDefault && (\n <span className=\"ml-1.5 inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-action-primary-bg/10 text-primary font-medium\">\n {tr(\"export.templates.card.default\", \"Default\")}\n </span>\n )}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={(e) => handleDeleteTemplate(e, template.id)}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-status-error-text hover:bg-status-error-bg/10 transition-colors\"\n aria-label={tr(\n \"export.templates.card.deleteAriaLabel\",\n \"Delete {name}\",\n { name: template.name },\n )}\n >\n <X size={14} />\n </button>\n <ChevronRight\n size={18}\n className=\"text-text-secondary group-hover:text-primary transition-colors mt-1\"\n />\n </div>\n </div>\n </div>\n\n {/* Body */}\n <div className=\"p-4\">\n {template.description && (\n <p className=\"text-sm text-text-secondary mb-3 line-clamp-2\">\n {template.description}\n </p>\n )}\n <div className=\"flex flex-wrap gap-1.5 overflow-hidden\">\n {template.entityTypes.slice(0, 6).map((entity) => (\n <span\n key={entity}\n className=\"inline-flex px-2 py-0.5 rounded text-xs font-medium bg-bg-sunken text-text-secondary max-w-full truncate\"\n >\n {entity}\n </span>\n ))}\n {template.entityTypes.length > 6 && (\n <span className=\"inline-flex px-2 py-0.5 rounded text-xs font-medium bg-bg-sunken text-text-secondary\">\n {tr(\n \"export.templates.card.moreEntities\",\n \"+{count} more\",\n {\n count: template.entityTypes.length - 6,\n },\n )}\n </span>\n )}\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <IllustratedEmptyState\n illustration={searchQuery ? \"empty-search\" : \"empty-data\"}\n title={\n searchQuery\n ? tr(\n \"export.templates.empty.filtered.title\",\n \"No matching templates\",\n )\n : tr(\"export.templates.empty.title\", \"No templates yet\")\n }\n description={\n searchQuery\n ? tr(\n \"export.templates.empty.filtered.description\",\n \"Try adjusting your search to find what you are looking for.\",\n )\n : tr(\n \"export.templates.empty.description\",\n \"Create a template to save reusable export configurations.\",\n )\n }\n action={\n searchQuery ? (\n <button\n type=\"button\"\n onClick={() => setSearchQuery(\"\")}\n className=\"inline-flex items-center gap-2 px-4 py-2 border-2 border-border-subtle rounded-lg hover:bg-accent transition-colors font-medium\"\n >\n <X size={16} />\n {tr(\n \"export.templates.empty.filtered.actions.clear\",\n \"Clear Search\",\n )}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={handleOpenCreate}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors font-medium\"\n >\n <Plus size={16} />\n {tr(\n \"export.templates.empty.actions.newTemplate\",\n \"New Template\",\n )}\n </button>\n )\n }\n />\n )\n ) : null}\n {templatePendingDelete && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"template-list-delete-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=\"template-list-delete-title\" className=\"text-lg font-bold\">\n {tr(\"export.templates.delete.title\", \"Delete template?\")}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {tr(\n \"export.templates.delete.description\",\n \"This removes the saved export configuration. Existing export jobs are not deleted.\",\n )}\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={() => setTemplatePendingDelete(null)}\n className=\"px-4 py-2 rounded-lg border-2 border-border-subtle hover:bg-accent transition-colors font-medium\"\n >\n {tr(\"export.templates.delete.cancel\", \"Keep Template\")}\n </button>\n <button\n type=\"button\"\n onClick={confirmDeleteTemplate}\n className=\"px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors font-medium\"\n >\n {tr(\"export.templates.delete.confirmAction\", \"Delete Template\")}\n </button>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|