@insureco/docman 1.0.0 → 1.0.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/chunk-DBVEE4WX.js +457 -0
- package/dist/chunk-DBVEE4WX.js.map +1 -0
- package/dist/client-CdjgCtrw.d.cts +100 -0
- package/dist/client-nlarq_ao.d.ts +100 -0
- package/dist/index.cjs +19 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -95
- package/dist/index.d.ts +3 -95
- package/dist/index.js +3 -435
- package/dist/index.js.map +1 -1
- package/dist/mock.cjs +8 -0
- package/dist/mock.cjs.map +1 -1
- package/dist/mock.d.cts +2 -1
- package/dist/mock.d.ts +2 -1
- package/dist/mock.js +8 -0
- package/dist/mock.js.map +1 -1
- package/dist/{types-xbeSIrNB.d.cts → types-DvQ8gX3k.d.cts} +1 -1
- package/dist/{types-xbeSIrNB.d.ts → types-DvQ8gX3k.d.ts} +1 -1
- package/dist/ui/index.cjs +1938 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.cts +122 -0
- package/dist/ui/index.d.ts +122 -0
- package/dist/ui/index.js +1441 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +24 -8
|
@@ -0,0 +1,1938 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/ui/index.ts
|
|
21
|
+
var ui_exports = {};
|
|
22
|
+
__export(ui_exports, {
|
|
23
|
+
DocmanProvider: () => DocmanProvider,
|
|
24
|
+
DocumentGenerator: () => DocumentGenerator,
|
|
25
|
+
DocumentList: () => DocumentList,
|
|
26
|
+
HtmlTemplateEditor: () => HtmlTemplateEditor,
|
|
27
|
+
HtmlTemplateList: () => HtmlTemplateList,
|
|
28
|
+
OperationsPanel: () => OperationsPanel,
|
|
29
|
+
PdfTemplateList: () => PdfTemplateList,
|
|
30
|
+
PdfTemplateUploader: () => PdfTemplateUploader,
|
|
31
|
+
TemplateGroupList: () => TemplateGroupList,
|
|
32
|
+
useDocmanClient: () => useDocmanClient,
|
|
33
|
+
useDocuments: () => useDocuments,
|
|
34
|
+
useHtmlTemplates: () => useHtmlTemplates,
|
|
35
|
+
usePdfTemplates: () => usePdfTemplates,
|
|
36
|
+
useTemplateGroups: () => useTemplateGroups
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(ui_exports);
|
|
39
|
+
|
|
40
|
+
// src/ui/context.tsx
|
|
41
|
+
var import_react = require("react");
|
|
42
|
+
|
|
43
|
+
// src/errors.ts
|
|
44
|
+
var DocmanError = class extends Error {
|
|
45
|
+
constructor(message, statusCode, code, details) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.statusCode = statusCode;
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.details = details;
|
|
50
|
+
this.name = "DocmanError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/client.ts
|
|
55
|
+
function toUint8(b) {
|
|
56
|
+
return b;
|
|
57
|
+
}
|
|
58
|
+
var DEFAULT_RETRIES = 2;
|
|
59
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
60
|
+
var TOKEN_BUFFER_SECONDS = 30;
|
|
61
|
+
function sleep(ms) {
|
|
62
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
|
+
}
|
|
64
|
+
function backoff(attempt) {
|
|
65
|
+
const base = Math.min(1e3 * 2 ** attempt, 5e3);
|
|
66
|
+
return base * (0.5 + Math.random() * 0.5);
|
|
67
|
+
}
|
|
68
|
+
function isTokenExpired(token, bufferSeconds = TOKEN_BUFFER_SECONDS) {
|
|
69
|
+
try {
|
|
70
|
+
const parts = token.split(".");
|
|
71
|
+
if (parts.length !== 3) return true;
|
|
72
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
73
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
74
|
+
return (payload.exp ?? 0) <= now + bufferSeconds;
|
|
75
|
+
} catch {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function extractError(envelope) {
|
|
80
|
+
const err = envelope.error;
|
|
81
|
+
if (typeof err === "object" && err !== null) return err;
|
|
82
|
+
return { code: "REQUEST_ERROR", message: err ?? "Request failed" };
|
|
83
|
+
}
|
|
84
|
+
var DocmanClient = class _DocmanClient {
|
|
85
|
+
baseUrl;
|
|
86
|
+
accessTokenFn;
|
|
87
|
+
internalKey;
|
|
88
|
+
retries;
|
|
89
|
+
timeoutMs;
|
|
90
|
+
cachedToken = null;
|
|
91
|
+
constructor(config) {
|
|
92
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
93
|
+
this.accessTokenFn = config.accessTokenFn;
|
|
94
|
+
this.internalKey = config.internalKey;
|
|
95
|
+
this.retries = config.retries ?? DEFAULT_RETRIES;
|
|
96
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create a client from environment variables.
|
|
100
|
+
*
|
|
101
|
+
* Reads DOCMAN_URL (injected automatically when you declare docman as an
|
|
102
|
+
* internalDependency in catalog-info.yaml). Auth priority:
|
|
103
|
+
* 1. BIO_CLIENT_ID + BIO_CLIENT_SECRET → client_credentials token
|
|
104
|
+
* 2. INTERNAL_SERVICE_KEY → internal key header
|
|
105
|
+
* 3. No auth → local dev / testing
|
|
106
|
+
*
|
|
107
|
+
* catalog-info.yaml:
|
|
108
|
+
* spec:
|
|
109
|
+
* internalDependencies:
|
|
110
|
+
* - service: docman
|
|
111
|
+
* port: 3000
|
|
112
|
+
*/
|
|
113
|
+
static fromEnv() {
|
|
114
|
+
const baseUrl = process.env.DOCMAN_URL;
|
|
115
|
+
if (!baseUrl) {
|
|
116
|
+
throw new DocmanError(
|
|
117
|
+
"DOCMAN_URL is not set. Add docman as an internalDependency in catalog-info.yaml",
|
|
118
|
+
500,
|
|
119
|
+
"CONFIG_ERROR"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const clientId = process.env.BIO_CLIENT_ID;
|
|
123
|
+
const clientSecret = process.env.BIO_CLIENT_SECRET;
|
|
124
|
+
const bioIdUrl = process.env.BIO_ID_URL ?? "https://bio.tawa.insureco.io";
|
|
125
|
+
if (clientId && clientSecret) {
|
|
126
|
+
return new _DocmanClient({
|
|
127
|
+
baseUrl,
|
|
128
|
+
accessTokenFn: async () => {
|
|
129
|
+
const res = await fetch(`${bioIdUrl}/api/oauth/token`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: { "Content-Type": "application/json" },
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
grant_type: "client_credentials",
|
|
134
|
+
client_id: clientId,
|
|
135
|
+
client_secret: clientSecret
|
|
136
|
+
})
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok) {
|
|
139
|
+
throw new DocmanError("Failed to obtain Bio-ID access token", res.status, "AUTH_ERROR");
|
|
140
|
+
}
|
|
141
|
+
const data = await res.json();
|
|
142
|
+
return data.access_token;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const internalKey = process.env.INTERNAL_SERVICE_KEY;
|
|
147
|
+
if (internalKey) {
|
|
148
|
+
return new _DocmanClient({ baseUrl, internalKey });
|
|
149
|
+
}
|
|
150
|
+
return new _DocmanClient({ baseUrl });
|
|
151
|
+
}
|
|
152
|
+
// ─── Auth ────────────────────────────────────────────────
|
|
153
|
+
async authHeaders() {
|
|
154
|
+
if (this.accessTokenFn) {
|
|
155
|
+
if (!this.cachedToken || isTokenExpired(this.cachedToken)) {
|
|
156
|
+
this.cachedToken = await this.accessTokenFn();
|
|
157
|
+
}
|
|
158
|
+
return { Authorization: `Bearer ${this.cachedToken}` };
|
|
159
|
+
}
|
|
160
|
+
if (this.internalKey) {
|
|
161
|
+
return { "X-Internal-Key": this.internalKey };
|
|
162
|
+
}
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
// ─── Core fetch ──────────────────────────────────────────
|
|
166
|
+
async rawFetch(method, path, body, attempt = 0) {
|
|
167
|
+
const headers = await this.authHeaders();
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
170
|
+
let response;
|
|
171
|
+
try {
|
|
172
|
+
response = await fetch(`${this.baseUrl}${path}`, {
|
|
173
|
+
method,
|
|
174
|
+
headers: body !== void 0 ? { ...headers, "Content-Type": "application/json" } : headers,
|
|
175
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
176
|
+
signal: controller.signal
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
if (attempt < this.retries) {
|
|
181
|
+
await sleep(backoff(attempt));
|
|
182
|
+
return this.rawFetch(method, path, body, attempt + 1);
|
|
183
|
+
}
|
|
184
|
+
throw new DocmanError(`Network error: ${err.message}`, 0, "NETWORK_ERROR");
|
|
185
|
+
} finally {
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
}
|
|
188
|
+
if (response.status >= 500 && attempt < this.retries) {
|
|
189
|
+
await sleep(backoff(attempt));
|
|
190
|
+
return this.rawFetch(method, path, body, attempt + 1);
|
|
191
|
+
}
|
|
192
|
+
const envelope = await response.json();
|
|
193
|
+
if (!response.ok || !envelope.success) {
|
|
194
|
+
const { code, message } = extractError(envelope);
|
|
195
|
+
throw new DocmanError(message, response.status, code);
|
|
196
|
+
}
|
|
197
|
+
return envelope;
|
|
198
|
+
}
|
|
199
|
+
async fetchData(method, path, body) {
|
|
200
|
+
const envelope = await this.rawFetch(method, path, body);
|
|
201
|
+
return envelope.data;
|
|
202
|
+
}
|
|
203
|
+
async fetchList(path) {
|
|
204
|
+
const envelope = await this.rawFetch("GET", path);
|
|
205
|
+
return {
|
|
206
|
+
data: envelope.data ?? [],
|
|
207
|
+
meta: envelope.meta ?? { total: 0, page: 1, limit: 20 }
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
buildQuery(params) {
|
|
211
|
+
const pairs = Object.entries(params).filter(([, v]) => v !== void 0).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
|
|
212
|
+
return pairs.length > 0 ? `?${pairs.join("&")}` : "";
|
|
213
|
+
}
|
|
214
|
+
/** Fetch with FormData body (multipart), return raw binary buffer. */
|
|
215
|
+
async fetchBinary(method, path, formData, attempt = 0) {
|
|
216
|
+
const headers = await this.authHeaders();
|
|
217
|
+
const controller = new AbortController();
|
|
218
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
219
|
+
let response;
|
|
220
|
+
try {
|
|
221
|
+
response = await fetch(`${this.baseUrl}${path}`, {
|
|
222
|
+
method,
|
|
223
|
+
headers,
|
|
224
|
+
body: formData,
|
|
225
|
+
signal: controller.signal
|
|
226
|
+
});
|
|
227
|
+
} catch (err) {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
if (attempt < this.retries) {
|
|
230
|
+
await sleep(backoff(attempt));
|
|
231
|
+
return this.fetchBinary(method, path, formData, attempt + 1);
|
|
232
|
+
}
|
|
233
|
+
throw new DocmanError(`Network error: ${err.message}`, 0, "NETWORK_ERROR");
|
|
234
|
+
} finally {
|
|
235
|
+
clearTimeout(timer);
|
|
236
|
+
}
|
|
237
|
+
if (response.status >= 500 && attempt < this.retries) {
|
|
238
|
+
await sleep(backoff(attempt));
|
|
239
|
+
return this.fetchBinary(method, path, formData, attempt + 1);
|
|
240
|
+
}
|
|
241
|
+
if (!response.ok) {
|
|
242
|
+
const envelope = await response.json();
|
|
243
|
+
const { code, message } = extractError(envelope);
|
|
244
|
+
throw new DocmanError(message, response.status, code);
|
|
245
|
+
}
|
|
246
|
+
return Buffer.from(await response.arrayBuffer());
|
|
247
|
+
}
|
|
248
|
+
/** POST multipart form and parse the JSON response envelope. */
|
|
249
|
+
async fetchMultipartJson(path, formData) {
|
|
250
|
+
const headers = await this.authHeaders();
|
|
251
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers,
|
|
254
|
+
body: formData
|
|
255
|
+
});
|
|
256
|
+
const envelope = await response.json();
|
|
257
|
+
if (!response.ok || !envelope.success) {
|
|
258
|
+
const { code, message } = extractError(envelope);
|
|
259
|
+
throw new DocmanError(message, response.status, code);
|
|
260
|
+
}
|
|
261
|
+
return envelope.data;
|
|
262
|
+
}
|
|
263
|
+
/** PUT multipart form and parse the JSON response envelope. */
|
|
264
|
+
async putMultipartJson(path, formData) {
|
|
265
|
+
const headers = await this.authHeaders();
|
|
266
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
267
|
+
method: "PUT",
|
|
268
|
+
headers,
|
|
269
|
+
body: formData
|
|
270
|
+
});
|
|
271
|
+
const envelope = await response.json();
|
|
272
|
+
if (!response.ok || !envelope.success) {
|
|
273
|
+
const { code, message } = extractError(envelope);
|
|
274
|
+
throw new DocmanError(message, response.status, code);
|
|
275
|
+
}
|
|
276
|
+
return envelope.data;
|
|
277
|
+
}
|
|
278
|
+
// ─── HTML Templates ──────────────────────────────────────
|
|
279
|
+
async createHtmlTemplate(options) {
|
|
280
|
+
return this.fetchData("POST", "/templates/html", options);
|
|
281
|
+
}
|
|
282
|
+
async listHtmlTemplates(query = {}) {
|
|
283
|
+
return this.fetchList(`/templates/html${this.buildQuery(query)}`);
|
|
284
|
+
}
|
|
285
|
+
async getHtmlTemplate(id) {
|
|
286
|
+
return this.fetchData("GET", `/templates/html/${encodeURIComponent(id)}`);
|
|
287
|
+
}
|
|
288
|
+
async updateHtmlTemplate(id, options) {
|
|
289
|
+
return this.fetchData("PUT", `/templates/html/${encodeURIComponent(id)}`, options);
|
|
290
|
+
}
|
|
291
|
+
async deleteHtmlTemplate(id) {
|
|
292
|
+
return this.fetchData("DELETE", `/templates/html/${encodeURIComponent(id)}`);
|
|
293
|
+
}
|
|
294
|
+
// ─── PDF Templates ───────────────────────────────────────
|
|
295
|
+
/**
|
|
296
|
+
* Upload a PDF form template.
|
|
297
|
+
* @param file Raw PDF bytes (Buffer or Uint8Array)
|
|
298
|
+
* @param options Name, description, and optional coordinates/isSystem flags
|
|
299
|
+
*/
|
|
300
|
+
async uploadPdfTemplate(file, options = {}) {
|
|
301
|
+
const form = new FormData();
|
|
302
|
+
const filename = options.name ?? "template.pdf";
|
|
303
|
+
form.append("file", new Blob([toUint8(file)], { type: "application/pdf" }), filename);
|
|
304
|
+
if (options.name) form.append("name", options.name);
|
|
305
|
+
if (options.description) form.append("description", options.description);
|
|
306
|
+
if (options.coordinates) form.append("coordinates", JSON.stringify(options.coordinates));
|
|
307
|
+
if (options.isSystem !== void 0) form.append("isSystem", String(options.isSystem));
|
|
308
|
+
return this.fetchMultipartJson("/templates/pdf", form);
|
|
309
|
+
}
|
|
310
|
+
async listPdfTemplates(query = {}) {
|
|
311
|
+
return this.fetchList(`/templates/pdf${this.buildQuery(query)}`);
|
|
312
|
+
}
|
|
313
|
+
async getPdfTemplate(id) {
|
|
314
|
+
return this.fetchData("GET", `/templates/pdf/${encodeURIComponent(id)}`);
|
|
315
|
+
}
|
|
316
|
+
async getPdfTemplateFields(id) {
|
|
317
|
+
return this.fetchData("GET", `/templates/pdf/${encodeURIComponent(id)}/fields`);
|
|
318
|
+
}
|
|
319
|
+
async getPdfTemplateDownloadUrl(id) {
|
|
320
|
+
const data = await this.fetchData("GET", `/templates/pdf/${encodeURIComponent(id)}/download`);
|
|
321
|
+
return data.url;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Update a PDF template's metadata, and optionally replace the PDF file.
|
|
325
|
+
* @param file If provided, replaces the stored PDF and re-extracts form fields
|
|
326
|
+
*/
|
|
327
|
+
async updatePdfTemplate(id, options, file) {
|
|
328
|
+
if (file) {
|
|
329
|
+
const form = new FormData();
|
|
330
|
+
form.append("file", new Blob([toUint8(file)], { type: "application/pdf" }), "template.pdf");
|
|
331
|
+
if (options.name) form.append("name", options.name);
|
|
332
|
+
if (options.description) form.append("description", options.description);
|
|
333
|
+
if (options.coordinates) form.append("coordinates", JSON.stringify(options.coordinates));
|
|
334
|
+
return this.putMultipartJson(`/templates/pdf/${encodeURIComponent(id)}`, form);
|
|
335
|
+
}
|
|
336
|
+
return this.fetchData("PUT", `/templates/pdf/${encodeURIComponent(id)}`, options);
|
|
337
|
+
}
|
|
338
|
+
async deletePdfTemplate(id) {
|
|
339
|
+
return this.fetchData("DELETE", `/templates/pdf/${encodeURIComponent(id)}`);
|
|
340
|
+
}
|
|
341
|
+
// ─── Template Groups ─────────────────────────────────────
|
|
342
|
+
async createTemplateGroup(options) {
|
|
343
|
+
return this.fetchData("POST", "/template-groups", options);
|
|
344
|
+
}
|
|
345
|
+
async listTemplateGroups(query = {}) {
|
|
346
|
+
return this.fetchList(`/template-groups${this.buildQuery(query)}`);
|
|
347
|
+
}
|
|
348
|
+
async getTemplateGroup(id) {
|
|
349
|
+
return this.fetchData("GET", `/template-groups/${encodeURIComponent(id)}`);
|
|
350
|
+
}
|
|
351
|
+
async updateTemplateGroup(id, options) {
|
|
352
|
+
return this.fetchData("PUT", `/template-groups/${encodeURIComponent(id)}`, options);
|
|
353
|
+
}
|
|
354
|
+
async deleteTemplateGroup(id) {
|
|
355
|
+
await this.fetchData("DELETE", `/template-groups/${encodeURIComponent(id)}`);
|
|
356
|
+
}
|
|
357
|
+
// ─── Document Generation ─────────────────────────────────
|
|
358
|
+
/**
|
|
359
|
+
* Generate a document and store it in S3.
|
|
360
|
+
* Returns a presigned download URL and a public share URL.
|
|
361
|
+
*/
|
|
362
|
+
async generate(options) {
|
|
363
|
+
return this.fetchData("POST", "/documents/generate", {
|
|
364
|
+
templateIds: options.templateIds,
|
|
365
|
+
templateData: options.data,
|
|
366
|
+
name: options.name,
|
|
367
|
+
description: options.description
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Generate a document and stream the PDF buffer directly.
|
|
372
|
+
* Nothing is stored — useful for previews or on-the-fly delivery.
|
|
373
|
+
*/
|
|
374
|
+
async generatePassthrough(options) {
|
|
375
|
+
const headers = await this.authHeaders();
|
|
376
|
+
const controller = new AbortController();
|
|
377
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
378
|
+
let response;
|
|
379
|
+
try {
|
|
380
|
+
response = await fetch(`${this.baseUrl}/documents/generate/passthrough`, {
|
|
381
|
+
method: "POST",
|
|
382
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
383
|
+
body: JSON.stringify({
|
|
384
|
+
templateIds: options.templateIds,
|
|
385
|
+
templateData: options.data,
|
|
386
|
+
name: options.name,
|
|
387
|
+
description: options.description
|
|
388
|
+
}),
|
|
389
|
+
signal: controller.signal
|
|
390
|
+
});
|
|
391
|
+
} catch (err) {
|
|
392
|
+
clearTimeout(timer);
|
|
393
|
+
throw new DocmanError(`Network error: ${err.message}`, 0, "NETWORK_ERROR");
|
|
394
|
+
} finally {
|
|
395
|
+
clearTimeout(timer);
|
|
396
|
+
}
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
const envelope = await response.json();
|
|
399
|
+
const { code, message } = extractError(envelope);
|
|
400
|
+
throw new DocmanError(message, response.status, code);
|
|
401
|
+
}
|
|
402
|
+
return Buffer.from(await response.arrayBuffer());
|
|
403
|
+
}
|
|
404
|
+
async listDocuments(query = {}) {
|
|
405
|
+
return this.fetchList(`/documents${this.buildQuery(query)}`);
|
|
406
|
+
}
|
|
407
|
+
async getDocument(id) {
|
|
408
|
+
return this.fetchData("GET", `/documents/${encodeURIComponent(id)}`);
|
|
409
|
+
}
|
|
410
|
+
/** Get a presigned S3 download URL for a stored document (1-hour TTL). */
|
|
411
|
+
async downloadUrl(id) {
|
|
412
|
+
const data = await this.fetchData("GET", `/documents/${encodeURIComponent(id)}/download`);
|
|
413
|
+
return data.url;
|
|
414
|
+
}
|
|
415
|
+
async getVersions(id) {
|
|
416
|
+
return this.fetchData("GET", `/documents/${encodeURIComponent(id)}/versions`);
|
|
417
|
+
}
|
|
418
|
+
/** Re-render a stored document using the original template data. */
|
|
419
|
+
async regenerate(id) {
|
|
420
|
+
return this.fetchData("POST", `/documents/${encodeURIComponent(id)}/regenerate`);
|
|
421
|
+
}
|
|
422
|
+
// ─── PDF Operations ──────────────────────────────────────
|
|
423
|
+
/**
|
|
424
|
+
* Merge two or more PDF buffers into a single PDF.
|
|
425
|
+
* Returns the merged PDF as a Buffer.
|
|
426
|
+
*/
|
|
427
|
+
async merge(buffers) {
|
|
428
|
+
if (buffers.length < 2) {
|
|
429
|
+
throw new DocmanError("At least 2 PDF buffers are required for merge", 400, "VALIDATION_ERROR");
|
|
430
|
+
}
|
|
431
|
+
const form = new FormData();
|
|
432
|
+
for (const buf of buffers) {
|
|
433
|
+
form.append("files", new Blob([toUint8(buf)], { type: "application/pdf" }), "file.pdf");
|
|
434
|
+
}
|
|
435
|
+
return this.fetchBinary("POST", "/operations/merge", form);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Split a PDF into parts by page ranges.
|
|
439
|
+
* Returns an array of SplitPart with buffer, range, and size info.
|
|
440
|
+
*/
|
|
441
|
+
async split(buffer, ranges) {
|
|
442
|
+
if (ranges.length === 0) {
|
|
443
|
+
throw new DocmanError("At least 1 page range is required", 400, "VALIDATION_ERROR");
|
|
444
|
+
}
|
|
445
|
+
const form = new FormData();
|
|
446
|
+
form.append("file", new Blob([toUint8(buffer)], { type: "application/pdf" }), "file.pdf");
|
|
447
|
+
form.append("ranges", JSON.stringify(ranges));
|
|
448
|
+
const headers = await this.authHeaders();
|
|
449
|
+
const response = await fetch(`${this.baseUrl}/operations/split`, {
|
|
450
|
+
method: "POST",
|
|
451
|
+
headers,
|
|
452
|
+
body: form
|
|
453
|
+
});
|
|
454
|
+
if (!response.ok) {
|
|
455
|
+
const envelope = await response.json();
|
|
456
|
+
const { code, message } = extractError(envelope);
|
|
457
|
+
throw new DocmanError(message, response.status, code);
|
|
458
|
+
}
|
|
459
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
460
|
+
if (contentType.includes("application/json")) {
|
|
461
|
+
const json = await response.json();
|
|
462
|
+
return json.data.map((part) => ({
|
|
463
|
+
index: part.index,
|
|
464
|
+
range: part.range,
|
|
465
|
+
buffer: Buffer.from(part.base64, "base64"),
|
|
466
|
+
sizeBytes: part.sizeBytes
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
const buf = Buffer.from(await response.arrayBuffer());
|
|
470
|
+
return [{ index: 0, range: ranges[0], buffer: buf, sizeBytes: buf.length }];
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Batch-introspect PDF form fields for multiple templates.
|
|
474
|
+
* Returns a map of templateId → field list.
|
|
475
|
+
*/
|
|
476
|
+
async introspectFields(templateIds) {
|
|
477
|
+
return this.fetchData(
|
|
478
|
+
"POST",
|
|
479
|
+
"/templates/pdf/fields",
|
|
480
|
+
{ templateIds }
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
/** Get the page count of a PDF buffer. */
|
|
484
|
+
async pageCount(buffer) {
|
|
485
|
+
const form = new FormData();
|
|
486
|
+
form.append("file", new Blob([toUint8(buffer)], { type: "application/pdf" }), "file.pdf");
|
|
487
|
+
const headers = await this.authHeaders();
|
|
488
|
+
const response = await fetch(`${this.baseUrl}/operations/page-count`, {
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers,
|
|
491
|
+
body: form
|
|
492
|
+
});
|
|
493
|
+
if (!response.ok) {
|
|
494
|
+
const envelope = await response.json();
|
|
495
|
+
const { code, message } = extractError(envelope);
|
|
496
|
+
throw new DocmanError(message, response.status, code);
|
|
497
|
+
}
|
|
498
|
+
const json = await response.json();
|
|
499
|
+
return json.data.pages;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/ui/context.tsx
|
|
504
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
505
|
+
var DocmanContext = (0, import_react.createContext)(null);
|
|
506
|
+
function DocmanProvider({ baseUrl, children }) {
|
|
507
|
+
const client = (0, import_react.useMemo)(() => new DocmanClient({ baseUrl }), [baseUrl]);
|
|
508
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DocmanContext.Provider, { value: { client }, children });
|
|
509
|
+
}
|
|
510
|
+
function useDocmanContext() {
|
|
511
|
+
const ctx = (0, import_react.useContext)(DocmanContext);
|
|
512
|
+
if (!ctx) {
|
|
513
|
+
throw new Error("useDocmanContext must be used inside <DocmanProvider>");
|
|
514
|
+
}
|
|
515
|
+
return ctx;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/ui/hooks/useDocmanClient.ts
|
|
519
|
+
function useDocmanClient() {
|
|
520
|
+
return useDocmanContext().client;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/ui/hooks/usePdfTemplates.ts
|
|
524
|
+
var import_react2 = require("react");
|
|
525
|
+
function usePdfTemplates(options = {}) {
|
|
526
|
+
const client = useDocmanClient();
|
|
527
|
+
const [result, setResult] = (0, import_react2.useState)(null);
|
|
528
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
529
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
530
|
+
const [tick, setTick] = (0, import_react2.useState)(0);
|
|
531
|
+
const refresh = (0, import_react2.useCallback)(() => setTick((t) => t + 1), []);
|
|
532
|
+
(0, import_react2.useEffect)(() => {
|
|
533
|
+
let cancelled = false;
|
|
534
|
+
setLoading(true);
|
|
535
|
+
setError(null);
|
|
536
|
+
client.listPdfTemplates({
|
|
537
|
+
page: options.page ?? 1,
|
|
538
|
+
limit: options.limit ?? 20,
|
|
539
|
+
archived: options.archived ?? false
|
|
540
|
+
}).then((res) => {
|
|
541
|
+
if (!cancelled) {
|
|
542
|
+
setResult(res);
|
|
543
|
+
setLoading(false);
|
|
544
|
+
}
|
|
545
|
+
}).catch((err) => {
|
|
546
|
+
if (!cancelled) {
|
|
547
|
+
setError(err.message);
|
|
548
|
+
setLoading(false);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
return () => {
|
|
552
|
+
cancelled = true;
|
|
553
|
+
};
|
|
554
|
+
}, [client, options.page, options.limit, options.archived, tick]);
|
|
555
|
+
return { result, loading, error, refresh };
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// src/ui/hooks/useHtmlTemplates.ts
|
|
559
|
+
var import_react3 = require("react");
|
|
560
|
+
function useHtmlTemplates(options = {}) {
|
|
561
|
+
const client = useDocmanClient();
|
|
562
|
+
const [result, setResult] = (0, import_react3.useState)(null);
|
|
563
|
+
const [loading, setLoading] = (0, import_react3.useState)(true);
|
|
564
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
565
|
+
const [tick, setTick] = (0, import_react3.useState)(0);
|
|
566
|
+
const refresh = (0, import_react3.useCallback)(() => setTick((t) => t + 1), []);
|
|
567
|
+
(0, import_react3.useEffect)(() => {
|
|
568
|
+
let cancelled = false;
|
|
569
|
+
setLoading(true);
|
|
570
|
+
setError(null);
|
|
571
|
+
client.listHtmlTemplates({
|
|
572
|
+
page: options.page ?? 1,
|
|
573
|
+
limit: options.limit ?? 20,
|
|
574
|
+
archived: options.archived ?? false
|
|
575
|
+
}).then((res) => {
|
|
576
|
+
if (!cancelled) {
|
|
577
|
+
setResult(res);
|
|
578
|
+
setLoading(false);
|
|
579
|
+
}
|
|
580
|
+
}).catch((err) => {
|
|
581
|
+
if (!cancelled) {
|
|
582
|
+
setError(err.message);
|
|
583
|
+
setLoading(false);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
return () => {
|
|
587
|
+
cancelled = true;
|
|
588
|
+
};
|
|
589
|
+
}, [client, options.page, options.limit, options.archived, tick]);
|
|
590
|
+
return { result, loading, error, refresh };
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/ui/hooks/useTemplateGroups.ts
|
|
594
|
+
var import_react4 = require("react");
|
|
595
|
+
function useTemplateGroups(page = 1, limit = 20) {
|
|
596
|
+
const client = useDocmanClient();
|
|
597
|
+
const [result, setResult] = (0, import_react4.useState)(null);
|
|
598
|
+
const [loading, setLoading] = (0, import_react4.useState)(true);
|
|
599
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
600
|
+
const [tick, setTick] = (0, import_react4.useState)(0);
|
|
601
|
+
const refresh = (0, import_react4.useCallback)(() => setTick((t) => t + 1), []);
|
|
602
|
+
(0, import_react4.useEffect)(() => {
|
|
603
|
+
let cancelled = false;
|
|
604
|
+
setLoading(true);
|
|
605
|
+
setError(null);
|
|
606
|
+
client.listTemplateGroups({ page, limit }).then((res) => {
|
|
607
|
+
if (!cancelled) {
|
|
608
|
+
setResult(res);
|
|
609
|
+
setLoading(false);
|
|
610
|
+
}
|
|
611
|
+
}).catch((err) => {
|
|
612
|
+
if (!cancelled) {
|
|
613
|
+
setError(err.message);
|
|
614
|
+
setLoading(false);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
return () => {
|
|
618
|
+
cancelled = true;
|
|
619
|
+
};
|
|
620
|
+
}, [client, page, limit, tick]);
|
|
621
|
+
return { result, loading, error, refresh };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/ui/hooks/useDocuments.ts
|
|
625
|
+
var import_react5 = require("react");
|
|
626
|
+
function useDocuments(page = 1, limit = 20) {
|
|
627
|
+
const client = useDocmanClient();
|
|
628
|
+
const [result, setResult] = (0, import_react5.useState)(null);
|
|
629
|
+
const [loading, setLoading] = (0, import_react5.useState)(true);
|
|
630
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
631
|
+
const [tick, setTick] = (0, import_react5.useState)(0);
|
|
632
|
+
const refresh = (0, import_react5.useCallback)(() => setTick((t) => t + 1), []);
|
|
633
|
+
(0, import_react5.useEffect)(() => {
|
|
634
|
+
let cancelled = false;
|
|
635
|
+
setLoading(true);
|
|
636
|
+
setError(null);
|
|
637
|
+
client.listDocuments({ page, limit }).then((res) => {
|
|
638
|
+
if (!cancelled) {
|
|
639
|
+
setResult(res);
|
|
640
|
+
setLoading(false);
|
|
641
|
+
}
|
|
642
|
+
}).catch((err) => {
|
|
643
|
+
if (!cancelled) {
|
|
644
|
+
setError(err.message);
|
|
645
|
+
setLoading(false);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
return () => {
|
|
649
|
+
cancelled = true;
|
|
650
|
+
};
|
|
651
|
+
}, [client, page, limit, tick]);
|
|
652
|
+
return { result, loading, error, refresh };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/ui/components/PdfTemplateList.tsx
|
|
656
|
+
var import_react7 = require("react");
|
|
657
|
+
|
|
658
|
+
// src/ui/shared/LoadingSpinner.tsx
|
|
659
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
660
|
+
function LoadingSpinner({ label = "Loading\u2026" }) {
|
|
661
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "docman-loading", children: [
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "docman-spinner", "aria-hidden": "true" }),
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: label })
|
|
664
|
+
] });
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/ui/shared/ErrorAlert.tsx
|
|
668
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
669
|
+
function ErrorAlert({ message }) {
|
|
670
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "docman-error", role: "alert", children: message });
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/ui/shared/EmptyState.tsx
|
|
674
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
675
|
+
function EmptyState({ icon = "\u{1F4C4}", title, description, action }) {
|
|
676
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "docman-empty", children: [
|
|
677
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "docman-empty-icon", "aria-hidden": "true", children: icon }),
|
|
678
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "docman-empty-title", children: title }),
|
|
679
|
+
description && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "docman-empty-desc", children: description }),
|
|
680
|
+
action && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { marginTop: "var(--docman-space-md)" }, children: action })
|
|
681
|
+
] });
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/ui/shared/Pagination.tsx
|
|
685
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
686
|
+
function Pagination({ page, total, limit, onPageChange }) {
|
|
687
|
+
const totalPages = Math.ceil(total / limit);
|
|
688
|
+
const start = (page - 1) * limit + 1;
|
|
689
|
+
const end = Math.min(page * limit, total);
|
|
690
|
+
if (totalPages <= 1) return null;
|
|
691
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "docman-pagination", children: [
|
|
692
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "docman-pagination-info", children: [
|
|
693
|
+
start,
|
|
694
|
+
"\u2013",
|
|
695
|
+
end,
|
|
696
|
+
" of ",
|
|
697
|
+
total
|
|
698
|
+
] }),
|
|
699
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "docman-pagination-controls", children: [
|
|
700
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
701
|
+
"button",
|
|
702
|
+
{
|
|
703
|
+
className: "docman-btn docman-btn-secondary docman-btn-sm",
|
|
704
|
+
onClick: () => onPageChange(page - 1),
|
|
705
|
+
disabled: page <= 1,
|
|
706
|
+
children: "Previous"
|
|
707
|
+
}
|
|
708
|
+
),
|
|
709
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
710
|
+
"button",
|
|
711
|
+
{
|
|
712
|
+
className: "docman-btn docman-btn-secondary docman-btn-sm",
|
|
713
|
+
onClick: () => onPageChange(page + 1),
|
|
714
|
+
disabled: page >= totalPages,
|
|
715
|
+
children: "Next"
|
|
716
|
+
}
|
|
717
|
+
)
|
|
718
|
+
] })
|
|
719
|
+
] });
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/ui/components/PdfTemplateUploader.tsx
|
|
723
|
+
var import_react6 = require("react");
|
|
724
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
725
|
+
function PdfTemplateUploader({ onSuccess, onCancel, className }) {
|
|
726
|
+
const client = useDocmanClient();
|
|
727
|
+
const inputRef = (0, import_react6.useRef)(null);
|
|
728
|
+
const [name, setName] = (0, import_react6.useState)("");
|
|
729
|
+
const [description, setDescription] = (0, import_react6.useState)("");
|
|
730
|
+
const [file, setFile] = (0, import_react6.useState)(null);
|
|
731
|
+
const [dragging, setDragging] = (0, import_react6.useState)(false);
|
|
732
|
+
const [uploading, setUploading] = (0, import_react6.useState)(false);
|
|
733
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
734
|
+
function handleFileChange(e) {
|
|
735
|
+
const f = e.target.files?.[0] ?? null;
|
|
736
|
+
setFile(f);
|
|
737
|
+
if (f && !name) setName(f.name.replace(/\.pdf$/i, ""));
|
|
738
|
+
}
|
|
739
|
+
function handleDrop(e) {
|
|
740
|
+
e.preventDefault();
|
|
741
|
+
setDragging(false);
|
|
742
|
+
const f = e.dataTransfer.files[0];
|
|
743
|
+
if (f?.type === "application/pdf") {
|
|
744
|
+
setFile(f);
|
|
745
|
+
if (!name) setName(f.name.replace(/\.pdf$/i, ""));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
async function handleSubmit() {
|
|
749
|
+
if (!file) {
|
|
750
|
+
setError("Please select a PDF file.");
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (!name.trim()) {
|
|
754
|
+
setError("Please enter a template name.");
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
setError(null);
|
|
758
|
+
setUploading(true);
|
|
759
|
+
try {
|
|
760
|
+
const buffer = await file.arrayBuffer();
|
|
761
|
+
const template = await client.uploadPdfTemplate(new Uint8Array(buffer), {
|
|
762
|
+
name: name.trim(),
|
|
763
|
+
description: description.trim() || void 0
|
|
764
|
+
});
|
|
765
|
+
onSuccess?.(template);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
setError(err.message);
|
|
768
|
+
} finally {
|
|
769
|
+
setUploading(false);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `docman-ui docman-card ${className ?? ""}`, children: [
|
|
773
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { style: { marginTop: 0, marginBottom: "var(--docman-space-lg)" }, children: "Upload PDF Template" }),
|
|
774
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorAlert, { message: error }),
|
|
775
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
776
|
+
"div",
|
|
777
|
+
{
|
|
778
|
+
className: `docman-dropzone ${dragging ? "docman-dropzone-active" : ""}`,
|
|
779
|
+
onClick: () => inputRef.current?.click(),
|
|
780
|
+
onDragOver: (e) => {
|
|
781
|
+
e.preventDefault();
|
|
782
|
+
setDragging(true);
|
|
783
|
+
},
|
|
784
|
+
onDragLeave: () => setDragging(false),
|
|
785
|
+
onDrop: handleDrop,
|
|
786
|
+
role: "button",
|
|
787
|
+
tabIndex: 0,
|
|
788
|
+
onKeyDown: (e) => e.key === "Enter" && inputRef.current?.click(),
|
|
789
|
+
"aria-label": "Click or drag to upload a PDF file",
|
|
790
|
+
children: [
|
|
791
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: "2rem" }, children: "\u{1F4CE}" }),
|
|
792
|
+
file ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontWeight: 500, marginTop: "var(--docman-space-sm)" }, children: file.name }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "docman-dropzone-text", children: "Click to select or drag a PDF here" }),
|
|
793
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
794
|
+
"input",
|
|
795
|
+
{
|
|
796
|
+
ref: inputRef,
|
|
797
|
+
type: "file",
|
|
798
|
+
accept: "application/pdf",
|
|
799
|
+
style: { display: "none" },
|
|
800
|
+
onChange: handleFileChange
|
|
801
|
+
}
|
|
802
|
+
)
|
|
803
|
+
]
|
|
804
|
+
}
|
|
805
|
+
),
|
|
806
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { marginTop: "var(--docman-space-lg)" }, children: [
|
|
807
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "docman-field", children: [
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { className: "docman-label", htmlFor: "pdf-upload-name", children: "Name *" }),
|
|
809
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
810
|
+
"input",
|
|
811
|
+
{
|
|
812
|
+
id: "pdf-upload-name",
|
|
813
|
+
className: "docman-input",
|
|
814
|
+
type: "text",
|
|
815
|
+
value: name,
|
|
816
|
+
onChange: (e) => setName(e.target.value),
|
|
817
|
+
placeholder: "Template name"
|
|
818
|
+
}
|
|
819
|
+
)
|
|
820
|
+
] }),
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "docman-field", children: [
|
|
822
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { className: "docman-label", htmlFor: "pdf-upload-desc", children: "Description" }),
|
|
823
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
824
|
+
"input",
|
|
825
|
+
{
|
|
826
|
+
id: "pdf-upload-desc",
|
|
827
|
+
className: "docman-input",
|
|
828
|
+
type: "text",
|
|
829
|
+
value: description,
|
|
830
|
+
onChange: (e) => setDescription(e.target.value),
|
|
831
|
+
placeholder: "Optional description"
|
|
832
|
+
}
|
|
833
|
+
)
|
|
834
|
+
] })
|
|
835
|
+
] }),
|
|
836
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "var(--docman-space-sm)", justifyContent: "flex-end" }, children: [
|
|
837
|
+
onCancel && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "docman-btn docman-btn-secondary", onClick: onCancel, disabled: uploading, children: "Cancel" }),
|
|
838
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
839
|
+
"button",
|
|
840
|
+
{
|
|
841
|
+
className: "docman-btn docman-btn-primary",
|
|
842
|
+
onClick: handleSubmit,
|
|
843
|
+
disabled: uploading || !file,
|
|
844
|
+
children: uploading ? "Uploading\u2026" : "Upload Template"
|
|
845
|
+
}
|
|
846
|
+
)
|
|
847
|
+
] })
|
|
848
|
+
] });
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/ui/components/PdfTemplateList.tsx
|
|
852
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
853
|
+
function PdfTemplateList({
|
|
854
|
+
onUploadSuccess,
|
|
855
|
+
showUploadButton = true,
|
|
856
|
+
pageSize = 20,
|
|
857
|
+
className
|
|
858
|
+
}) {
|
|
859
|
+
const client = useDocmanClient();
|
|
860
|
+
const [page, setPage] = (0, import_react7.useState)(1);
|
|
861
|
+
const [showUploader, setShowUploader] = (0, import_react7.useState)(false);
|
|
862
|
+
const [deleting, setDeleting] = (0, import_react7.useState)(null);
|
|
863
|
+
const [deleteError, setDeleteError] = (0, import_react7.useState)(null);
|
|
864
|
+
const { result, loading, error, refresh } = usePdfTemplates({ page, limit: pageSize });
|
|
865
|
+
async function handleDelete(template) {
|
|
866
|
+
if (!confirm(`Archive "${template.name}"? It will no longer appear in the list.`)) return;
|
|
867
|
+
setDeleteError(null);
|
|
868
|
+
setDeleting(template._id);
|
|
869
|
+
try {
|
|
870
|
+
await client.deletePdfTemplate(template._id);
|
|
871
|
+
refresh();
|
|
872
|
+
} catch (err) {
|
|
873
|
+
setDeleteError(err.message);
|
|
874
|
+
} finally {
|
|
875
|
+
setDeleting(null);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function handleUploadSuccess(template) {
|
|
879
|
+
setShowUploader(false);
|
|
880
|
+
refresh();
|
|
881
|
+
onUploadSuccess?.(template);
|
|
882
|
+
}
|
|
883
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `docman-ui ${className ?? ""}`, children: [
|
|
884
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "docman-header", children: [
|
|
885
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { className: "docman-title", children: "PDF Templates" }),
|
|
886
|
+
showUploadButton && !showUploader && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "docman-btn docman-btn-primary", onClick: () => setShowUploader(true), children: "+ Upload Template" })
|
|
887
|
+
] }),
|
|
888
|
+
showUploader && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { marginBottom: "var(--docman-space-lg)" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
889
|
+
PdfTemplateUploader,
|
|
890
|
+
{
|
|
891
|
+
onSuccess: handleUploadSuccess,
|
|
892
|
+
onCancel: () => setShowUploader(false)
|
|
893
|
+
}
|
|
894
|
+
) }),
|
|
895
|
+
deleteError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorAlert, { message: deleteError }),
|
|
896
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LoadingSpinner, {}),
|
|
897
|
+
!loading && error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorAlert, { message: error }),
|
|
898
|
+
!loading && !error && result && (result.data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
899
|
+
EmptyState,
|
|
900
|
+
{
|
|
901
|
+
icon: "\u{1F4CB}",
|
|
902
|
+
title: "No PDF templates yet",
|
|
903
|
+
description: "Upload a PDF form to get started.",
|
|
904
|
+
action: showUploadButton && !showUploader ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "docman-btn docman-btn-primary", onClick: () => setShowUploader(true), children: "Upload Template" }) : void 0
|
|
905
|
+
}
|
|
906
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
907
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "docman-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { className: "docman-table", children: [
|
|
908
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
|
|
909
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { children: "Name" }),
|
|
910
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { children: "Fields" }),
|
|
911
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { children: "Pages" }),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { children: "Created" }),
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", {})
|
|
914
|
+
] }) }),
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: result.data.map((t) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
|
|
916
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { children: [
|
|
917
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontWeight: 500 }, children: t.name }),
|
|
918
|
+
t.description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontSize: "0.8rem", color: "var(--docman-color-text-secondary)" }, children: t.description })
|
|
919
|
+
] }),
|
|
920
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "docman-badge docman-badge-info", children: [
|
|
921
|
+
t.fields.length,
|
|
922
|
+
" fields"
|
|
923
|
+
] }) }),
|
|
924
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { children: t.pages }),
|
|
925
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.8rem" }, children: new Date(t.createdAt).toLocaleDateString() }),
|
|
926
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { textAlign: "right" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
927
|
+
"button",
|
|
928
|
+
{
|
|
929
|
+
className: "docman-btn docman-btn-danger docman-btn-sm",
|
|
930
|
+
onClick: () => handleDelete(t),
|
|
931
|
+
disabled: deleting === t._id,
|
|
932
|
+
children: deleting === t._id ? "Archiving\u2026" : "Archive"
|
|
933
|
+
}
|
|
934
|
+
) })
|
|
935
|
+
] }, t._id)) })
|
|
936
|
+
] }) }),
|
|
937
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
938
|
+
Pagination,
|
|
939
|
+
{
|
|
940
|
+
page,
|
|
941
|
+
total: result.meta.total,
|
|
942
|
+
limit: pageSize,
|
|
943
|
+
onPageChange: setPage
|
|
944
|
+
}
|
|
945
|
+
)
|
|
946
|
+
] }))
|
|
947
|
+
] });
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/ui/components/HtmlTemplateList.tsx
|
|
951
|
+
var import_react9 = require("react");
|
|
952
|
+
|
|
953
|
+
// src/ui/components/HtmlTemplateEditor.tsx
|
|
954
|
+
var import_react8 = require("react");
|
|
955
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
956
|
+
var DEFAULT_HTML = `<!DOCTYPE html>
|
|
957
|
+
<html>
|
|
958
|
+
<head>
|
|
959
|
+
<meta charset="utf-8">
|
|
960
|
+
<style>
|
|
961
|
+
body { font-family: sans-serif; padding: 40px; }
|
|
962
|
+
h1 { color: #1e293b; }
|
|
963
|
+
</style>
|
|
964
|
+
</head>
|
|
965
|
+
<body>
|
|
966
|
+
<h1>Hello, {{firstName}}!</h1>
|
|
967
|
+
<p>Welcome to {{orgName}}.</p>
|
|
968
|
+
</body>
|
|
969
|
+
</html>`;
|
|
970
|
+
function HtmlTemplateEditor({
|
|
971
|
+
templateId,
|
|
972
|
+
initialHtml,
|
|
973
|
+
onSave,
|
|
974
|
+
onCancel,
|
|
975
|
+
className
|
|
976
|
+
}) {
|
|
977
|
+
const client = useDocmanClient();
|
|
978
|
+
const [name, setName] = (0, import_react8.useState)("");
|
|
979
|
+
const [description, setDescription] = (0, import_react8.useState)("");
|
|
980
|
+
const [html, setHtml] = (0, import_react8.useState)(initialHtml ?? DEFAULT_HTML);
|
|
981
|
+
const [landscape, setLandscape] = (0, import_react8.useState)(false);
|
|
982
|
+
const [loading, setLoading] = (0, import_react8.useState)(!!templateId);
|
|
983
|
+
const [saving, setSaving] = (0, import_react8.useState)(false);
|
|
984
|
+
const [error, setError] = (0, import_react8.useState)(null);
|
|
985
|
+
(0, import_react8.useEffect)(() => {
|
|
986
|
+
if (!templateId) return;
|
|
987
|
+
let cancelled = false;
|
|
988
|
+
setLoading(true);
|
|
989
|
+
client.getHtmlTemplate(templateId).then((t) => {
|
|
990
|
+
if (cancelled) return;
|
|
991
|
+
setName(t.name);
|
|
992
|
+
setDescription(t.description ?? "");
|
|
993
|
+
setHtml(t.html);
|
|
994
|
+
setLandscape(t.landscape);
|
|
995
|
+
setLoading(false);
|
|
996
|
+
}).catch((err) => {
|
|
997
|
+
if (cancelled) return;
|
|
998
|
+
setError(err.message);
|
|
999
|
+
setLoading(false);
|
|
1000
|
+
});
|
|
1001
|
+
return () => {
|
|
1002
|
+
cancelled = true;
|
|
1003
|
+
};
|
|
1004
|
+
}, [client, templateId]);
|
|
1005
|
+
async function handleSave() {
|
|
1006
|
+
if (!name.trim()) {
|
|
1007
|
+
setError("Please enter a template name.");
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
setError(null);
|
|
1011
|
+
setSaving(true);
|
|
1012
|
+
try {
|
|
1013
|
+
const template = templateId ? await client.updateHtmlTemplate(templateId, {
|
|
1014
|
+
name: name.trim(),
|
|
1015
|
+
description: description.trim() || void 0,
|
|
1016
|
+
html,
|
|
1017
|
+
landscape
|
|
1018
|
+
}) : await client.createHtmlTemplate({
|
|
1019
|
+
name: name.trim(),
|
|
1020
|
+
description: description.trim() || void 0,
|
|
1021
|
+
html,
|
|
1022
|
+
landscape
|
|
1023
|
+
});
|
|
1024
|
+
onSave?.(template);
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
setError(err.message);
|
|
1027
|
+
} finally {
|
|
1028
|
+
setSaving(false);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LoadingSpinner, {});
|
|
1032
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: `docman-ui ${className ?? ""}`, children: [
|
|
1033
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "docman-header", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { className: "docman-title", children: templateId ? "Edit HTML Template" : "New HTML Template" }) }),
|
|
1034
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorAlert, { message: error }),
|
|
1035
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "280px 1fr", gap: "var(--docman-space-lg)", alignItems: "start" }, children: [
|
|
1036
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
|
|
1037
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "docman-field", children: [
|
|
1038
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { className: "docman-label", htmlFor: "html-tmpl-name", children: "Name *" }),
|
|
1039
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1040
|
+
"input",
|
|
1041
|
+
{
|
|
1042
|
+
id: "html-tmpl-name",
|
|
1043
|
+
className: "docman-input",
|
|
1044
|
+
type: "text",
|
|
1045
|
+
value: name,
|
|
1046
|
+
onChange: (e) => setName(e.target.value),
|
|
1047
|
+
placeholder: "Template name"
|
|
1048
|
+
}
|
|
1049
|
+
)
|
|
1050
|
+
] }),
|
|
1051
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "docman-field", children: [
|
|
1052
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { className: "docman-label", htmlFor: "html-tmpl-desc", children: "Description" }),
|
|
1053
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1054
|
+
"input",
|
|
1055
|
+
{
|
|
1056
|
+
id: "html-tmpl-desc",
|
|
1057
|
+
className: "docman-input",
|
|
1058
|
+
type: "text",
|
|
1059
|
+
value: description,
|
|
1060
|
+
onChange: (e) => setDescription(e.target.value),
|
|
1061
|
+
placeholder: "Optional description"
|
|
1062
|
+
}
|
|
1063
|
+
)
|
|
1064
|
+
] }),
|
|
1065
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "docman-field", style: { display: "flex", alignItems: "center", gap: "var(--docman-space-sm)" }, children: [
|
|
1066
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1067
|
+
"input",
|
|
1068
|
+
{
|
|
1069
|
+
id: "html-tmpl-landscape",
|
|
1070
|
+
type: "checkbox",
|
|
1071
|
+
checked: landscape,
|
|
1072
|
+
onChange: (e) => setLandscape(e.target.checked)
|
|
1073
|
+
}
|
|
1074
|
+
),
|
|
1075
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "html-tmpl-landscape", className: "docman-label", style: { margin: 0 }, children: "Landscape orientation" })
|
|
1076
|
+
] }),
|
|
1077
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "var(--docman-space-sm)", marginTop: "var(--docman-space-lg)" }, children: [
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1079
|
+
"button",
|
|
1080
|
+
{
|
|
1081
|
+
className: "docman-btn docman-btn-primary",
|
|
1082
|
+
onClick: handleSave,
|
|
1083
|
+
disabled: saving,
|
|
1084
|
+
children: saving ? "Saving\u2026" : "Save Template"
|
|
1085
|
+
}
|
|
1086
|
+
),
|
|
1087
|
+
onCancel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { className: "docman-btn docman-btn-secondary", onClick: onCancel, disabled: saving, children: "Cancel" })
|
|
1088
|
+
] })
|
|
1089
|
+
] }),
|
|
1090
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "docman-split", children: [
|
|
1091
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
|
|
1092
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { className: "docman-label", children: "HTML (Handlebars supported)" }),
|
|
1093
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1094
|
+
"textarea",
|
|
1095
|
+
{
|
|
1096
|
+
className: "docman-textarea",
|
|
1097
|
+
style: { minHeight: "420px", fontFamily: "var(--docman-font-mono)", fontSize: "0.8rem" },
|
|
1098
|
+
value: html,
|
|
1099
|
+
onChange: (e) => setHtml(e.target.value),
|
|
1100
|
+
spellCheck: false
|
|
1101
|
+
}
|
|
1102
|
+
)
|
|
1103
|
+
] }),
|
|
1104
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
|
|
1105
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { className: "docman-label", children: "Live Preview" }),
|
|
1106
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1107
|
+
"iframe",
|
|
1108
|
+
{
|
|
1109
|
+
className: "docman-preview-frame",
|
|
1110
|
+
srcDoc: html,
|
|
1111
|
+
title: "Template preview",
|
|
1112
|
+
sandbox: "allow-same-origin"
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
] })
|
|
1116
|
+
] })
|
|
1117
|
+
] })
|
|
1118
|
+
] });
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/ui/components/HtmlTemplateList.tsx
|
|
1122
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1123
|
+
function HtmlTemplateList({
|
|
1124
|
+
onCreateSuccess,
|
|
1125
|
+
showCreateButton = true,
|
|
1126
|
+
pageSize = 20,
|
|
1127
|
+
className
|
|
1128
|
+
}) {
|
|
1129
|
+
const client = useDocmanClient();
|
|
1130
|
+
const [page, setPage] = (0, import_react9.useState)(1);
|
|
1131
|
+
const [creating, setCreating] = (0, import_react9.useState)(false);
|
|
1132
|
+
const [editingId, setEditingId] = (0, import_react9.useState)(null);
|
|
1133
|
+
const [deleting, setDeleting] = (0, import_react9.useState)(null);
|
|
1134
|
+
const [deleteError, setDeleteError] = (0, import_react9.useState)(null);
|
|
1135
|
+
const { result, loading, error, refresh } = useHtmlTemplates({ page, limit: pageSize });
|
|
1136
|
+
async function handleDelete(template) {
|
|
1137
|
+
if (!confirm(`Archive "${template.name}"?`)) return;
|
|
1138
|
+
setDeleteError(null);
|
|
1139
|
+
setDeleting(template._id);
|
|
1140
|
+
try {
|
|
1141
|
+
await client.deleteHtmlTemplate(template._id);
|
|
1142
|
+
refresh();
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
setDeleteError(err.message);
|
|
1145
|
+
} finally {
|
|
1146
|
+
setDeleting(null);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
function handleSaveSuccess(template) {
|
|
1150
|
+
setCreating(false);
|
|
1151
|
+
setEditingId(null);
|
|
1152
|
+
refresh();
|
|
1153
|
+
onCreateSuccess?.(template);
|
|
1154
|
+
}
|
|
1155
|
+
if (editingId) {
|
|
1156
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1157
|
+
HtmlTemplateEditor,
|
|
1158
|
+
{
|
|
1159
|
+
templateId: editingId,
|
|
1160
|
+
onSave: handleSaveSuccess,
|
|
1161
|
+
onCancel: () => setEditingId(null),
|
|
1162
|
+
className
|
|
1163
|
+
}
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
if (creating) {
|
|
1167
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1168
|
+
HtmlTemplateEditor,
|
|
1169
|
+
{
|
|
1170
|
+
onSave: handleSaveSuccess,
|
|
1171
|
+
onCancel: () => setCreating(false),
|
|
1172
|
+
className
|
|
1173
|
+
}
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `docman-ui ${className ?? ""}`, children: [
|
|
1177
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "docman-header", children: [
|
|
1178
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { className: "docman-title", children: "HTML Templates" }),
|
|
1179
|
+
showCreateButton && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { className: "docman-btn docman-btn-primary", onClick: () => setCreating(true), children: "+ New Template" })
|
|
1180
|
+
] }),
|
|
1181
|
+
deleteError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorAlert, { message: deleteError }),
|
|
1182
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LoadingSpinner, {}),
|
|
1183
|
+
!loading && error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorAlert, { message: error }),
|
|
1184
|
+
!loading && !error && result && (result.data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1185
|
+
EmptyState,
|
|
1186
|
+
{
|
|
1187
|
+
icon: "\u{1F58A}\uFE0F",
|
|
1188
|
+
title: "No HTML templates yet",
|
|
1189
|
+
description: "Create a Handlebars HTML template to generate PDF documents.",
|
|
1190
|
+
action: showCreateButton ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { className: "docman-btn docman-btn-primary", onClick: () => setCreating(true), children: "New Template" }) : void 0
|
|
1191
|
+
}
|
|
1192
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
1193
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "docman-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { className: "docman-table", children: [
|
|
1194
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
|
|
1195
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { children: "Name" }),
|
|
1196
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { children: "Variables" }),
|
|
1197
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { children: "Orientation" }),
|
|
1198
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { children: "Created" }),
|
|
1199
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", {})
|
|
1200
|
+
] }) }),
|
|
1201
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: result.data.map((t) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
|
|
1202
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { children: [
|
|
1203
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontWeight: 500 }, children: t.name }),
|
|
1204
|
+
t.description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "0.8rem", color: "var(--docman-color-text-secondary)" }, children: t.description })
|
|
1205
|
+
] }),
|
|
1206
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { children: t.variables.length > 0 ? t.variables.map((v) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "docman-badge docman-badge-info", style: { marginRight: "4px" }, children: `{{${v}}}` }, v)) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.8rem" }, children: "none" }) }),
|
|
1207
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { children: t.landscape ? "Landscape" : "Portrait" }),
|
|
1208
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.8rem" }, children: new Date(t.createdAt).toLocaleDateString() }),
|
|
1209
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { style: { textAlign: "right", display: "flex", gap: "var(--docman-space-xs)", justifyContent: "flex-end" }, children: [
|
|
1210
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1211
|
+
"button",
|
|
1212
|
+
{
|
|
1213
|
+
className: "docman-btn docman-btn-secondary docman-btn-sm",
|
|
1214
|
+
onClick: () => setEditingId(t._id),
|
|
1215
|
+
children: "Edit"
|
|
1216
|
+
}
|
|
1217
|
+
),
|
|
1218
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1219
|
+
"button",
|
|
1220
|
+
{
|
|
1221
|
+
className: "docman-btn docman-btn-danger docman-btn-sm",
|
|
1222
|
+
onClick: () => handleDelete(t),
|
|
1223
|
+
disabled: deleting === t._id,
|
|
1224
|
+
children: deleting === t._id ? "Archiving\u2026" : "Archive"
|
|
1225
|
+
}
|
|
1226
|
+
)
|
|
1227
|
+
] })
|
|
1228
|
+
] }, t._id)) })
|
|
1229
|
+
] }) }),
|
|
1230
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pagination, { page, total: result.meta.total, limit: pageSize, onPageChange: setPage })
|
|
1231
|
+
] }))
|
|
1232
|
+
] });
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// src/ui/components/TemplateGroupList.tsx
|
|
1236
|
+
var import_react10 = require("react");
|
|
1237
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1238
|
+
function TemplateGroupList({ onSelect, pageSize = 20, className }) {
|
|
1239
|
+
const [page, setPage] = (0, import_react10.useState)(1);
|
|
1240
|
+
const { result, loading, error } = useTemplateGroups(page, pageSize);
|
|
1241
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `docman-ui ${className ?? ""}`, children: [
|
|
1242
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "docman-header", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { className: "docman-title", children: "Template Groups" }) }),
|
|
1243
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(LoadingSpinner, {}),
|
|
1244
|
+
!loading && error && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ErrorAlert, { message: error }),
|
|
1245
|
+
!loading && !error && result && (result.data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1246
|
+
EmptyState,
|
|
1247
|
+
{
|
|
1248
|
+
icon: "\u{1F4C1}",
|
|
1249
|
+
title: "No template groups yet",
|
|
1250
|
+
description: "Template groups combine multiple PDF and HTML templates into a single document bundle."
|
|
1251
|
+
}
|
|
1252
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
1253
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "docman-checkbox-grid", children: result.data.map((g) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1254
|
+
"div",
|
|
1255
|
+
{
|
|
1256
|
+
className: "docman-checkbox-card",
|
|
1257
|
+
onClick: () => onSelect?.(g),
|
|
1258
|
+
role: onSelect ? "button" : void 0,
|
|
1259
|
+
tabIndex: onSelect ? 0 : void 0,
|
|
1260
|
+
onKeyDown: (e) => e.key === "Enter" && onSelect?.(g),
|
|
1261
|
+
style: { cursor: onSelect ? "pointer" : "default" },
|
|
1262
|
+
children: [
|
|
1263
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { fontSize: "1.5rem" }, children: "\u{1F4C1}" }),
|
|
1264
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
|
|
1265
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { fontWeight: 600, fontSize: "0.875rem" }, children: g.name }),
|
|
1266
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { fontSize: "0.8rem", color: "var(--docman-color-text-secondary)", marginTop: "2px" }, children: [
|
|
1267
|
+
g.templates.length,
|
|
1268
|
+
" template",
|
|
1269
|
+
g.templates.length !== 1 ? "s" : ""
|
|
1270
|
+
] }),
|
|
1271
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { display: "flex", gap: "4px", flexWrap: "wrap", marginTop: "var(--docman-space-xs)" }, children: g.templates.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: `docman-badge ${t.templateType === "pdf" ? "docman-badge-info" : "docman-badge-success"}`, children: t.templateType.toUpperCase() }, i)) })
|
|
1272
|
+
] })
|
|
1273
|
+
]
|
|
1274
|
+
},
|
|
1275
|
+
g._id
|
|
1276
|
+
)) }),
|
|
1277
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pagination, { page, total: result.meta.total, limit: pageSize, onPageChange: setPage })
|
|
1278
|
+
] }))
|
|
1279
|
+
] });
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/ui/components/DocumentList.tsx
|
|
1283
|
+
var import_react11 = require("react");
|
|
1284
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1285
|
+
function formatBytes(bytes) {
|
|
1286
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1287
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1288
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1289
|
+
}
|
|
1290
|
+
function DocumentList({ pageSize = 20, className }) {
|
|
1291
|
+
const client = useDocmanClient();
|
|
1292
|
+
const [page, setPage] = (0, import_react11.useState)(1);
|
|
1293
|
+
const [downloading, setDownloading] = (0, import_react11.useState)(null);
|
|
1294
|
+
const [downloadError, setDownloadError] = (0, import_react11.useState)(null);
|
|
1295
|
+
const { result, loading, error } = useDocuments(page, pageSize);
|
|
1296
|
+
async function handleDownload(id, name) {
|
|
1297
|
+
setDownloadError(null);
|
|
1298
|
+
setDownloading(id);
|
|
1299
|
+
try {
|
|
1300
|
+
const url = await client.downloadUrl(id);
|
|
1301
|
+
const a = document.createElement("a");
|
|
1302
|
+
a.href = url;
|
|
1303
|
+
a.download = `${name}.pdf`;
|
|
1304
|
+
a.click();
|
|
1305
|
+
} catch (err) {
|
|
1306
|
+
setDownloadError(err.message);
|
|
1307
|
+
} finally {
|
|
1308
|
+
setDownloading(null);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `docman-ui ${className ?? ""}`, children: [
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "docman-header", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "docman-title", children: "Documents" }) }),
|
|
1313
|
+
downloadError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ErrorAlert, { message: downloadError }),
|
|
1314
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(LoadingSpinner, {}),
|
|
1315
|
+
!loading && error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ErrorAlert, { message: error }),
|
|
1316
|
+
!loading && !error && result && (result.data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1317
|
+
EmptyState,
|
|
1318
|
+
{
|
|
1319
|
+
icon: "\u{1F4C4}",
|
|
1320
|
+
title: "No documents yet",
|
|
1321
|
+
description: "Generate your first document using the Generate tab."
|
|
1322
|
+
}
|
|
1323
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
1324
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "docman-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { className: "docman-table", children: [
|
|
1325
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
|
|
1326
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { children: "Name" }),
|
|
1327
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { children: "Pages" }),
|
|
1328
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { children: "Size" }),
|
|
1329
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { children: "Version" }),
|
|
1330
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { children: "Created" }),
|
|
1331
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", {})
|
|
1332
|
+
] }) }),
|
|
1333
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: result.data.map((doc) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
|
|
1334
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("td", { children: [
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontWeight: 500 }, children: doc.name }),
|
|
1336
|
+
doc.description && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: "0.8rem", color: "var(--docman-color-text-secondary)" }, children: doc.description })
|
|
1337
|
+
] }),
|
|
1338
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { children: doc.pages }),
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.8rem" }, children: formatBytes(doc.sizeBytes) }),
|
|
1340
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "docman-badge docman-badge-info", children: [
|
|
1341
|
+
"v",
|
|
1342
|
+
doc.version
|
|
1343
|
+
] }) }),
|
|
1344
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.8rem" }, children: new Date(doc.createdAt).toLocaleDateString() }),
|
|
1345
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("td", { style: { textAlign: "right", display: "flex", gap: "var(--docman-space-xs)", justifyContent: "flex-end" }, children: [
|
|
1346
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1347
|
+
"button",
|
|
1348
|
+
{
|
|
1349
|
+
className: "docman-btn docman-btn-secondary docman-btn-sm",
|
|
1350
|
+
onClick: () => handleDownload(doc._id, doc.name),
|
|
1351
|
+
disabled: downloading === doc._id,
|
|
1352
|
+
children: downloading === doc._id ? "Fetching\u2026" : "Download"
|
|
1353
|
+
}
|
|
1354
|
+
),
|
|
1355
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1356
|
+
"a",
|
|
1357
|
+
{
|
|
1358
|
+
href: `/d/${doc.shareToken}`,
|
|
1359
|
+
target: "_blank",
|
|
1360
|
+
rel: "noopener noreferrer",
|
|
1361
|
+
className: "docman-btn docman-btn-secondary docman-btn-sm",
|
|
1362
|
+
children: "Share"
|
|
1363
|
+
}
|
|
1364
|
+
)
|
|
1365
|
+
] })
|
|
1366
|
+
] }, doc._id)) })
|
|
1367
|
+
] }) }),
|
|
1368
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pagination, { page, total: result.meta.total, limit: pageSize, onPageChange: setPage })
|
|
1369
|
+
] }))
|
|
1370
|
+
] });
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// src/ui/components/DocumentGenerator.tsx
|
|
1374
|
+
var import_react12 = require("react");
|
|
1375
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1376
|
+
function StepIndicator({ current }) {
|
|
1377
|
+
const steps = [
|
|
1378
|
+
{ key: "select", label: "Select templates" },
|
|
1379
|
+
{ key: "fields", label: "Fill in data" },
|
|
1380
|
+
{ key: "generate", label: "Done" }
|
|
1381
|
+
];
|
|
1382
|
+
const order = ["select", "fields", "generate"];
|
|
1383
|
+
const currentIdx = order.indexOf(current);
|
|
1384
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "docman-steps", children: steps.map(({ key, label }, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1385
|
+
"div",
|
|
1386
|
+
{
|
|
1387
|
+
className: `docman-step ${i === currentIdx ? "docman-step-active" : ""} ${i < currentIdx ? "docman-step-done" : ""}`,
|
|
1388
|
+
children: [
|
|
1389
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "docman-step-num", children: i < currentIdx ? "\u2713" : i + 1 }),
|
|
1390
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: label }),
|
|
1391
|
+
i < steps.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { color: "var(--docman-color-border)", margin: "0 4px" }, children: "\u2192" })
|
|
1392
|
+
]
|
|
1393
|
+
},
|
|
1394
|
+
key
|
|
1395
|
+
)) });
|
|
1396
|
+
}
|
|
1397
|
+
function DocumentGenerator({ initialTemplateIds, onSuccess, className }) {
|
|
1398
|
+
const client = useDocmanClient();
|
|
1399
|
+
const [step, setStep] = (0, import_react12.useState)(initialTemplateIds ? "fields" : "select");
|
|
1400
|
+
const [selectedIds, setSelectedIds] = (0, import_react12.useState)(initialTemplateIds ? [...initialTemplateIds] : []);
|
|
1401
|
+
const [fields, setFields] = (0, import_react12.useState)({});
|
|
1402
|
+
const [htmlVariables, setHtmlVariables] = (0, import_react12.useState)([]);
|
|
1403
|
+
const [fieldValues, setFieldValues] = (0, import_react12.useState)({});
|
|
1404
|
+
const [introspecting, setIntrospecting] = (0, import_react12.useState)(false);
|
|
1405
|
+
const [introspectError, setIntrospectError] = (0, import_react12.useState)(null);
|
|
1406
|
+
const [docName, setDocName] = (0, import_react12.useState)("");
|
|
1407
|
+
const [docDescription, setDocDescription] = (0, import_react12.useState)("");
|
|
1408
|
+
const [generating, setGenerating] = (0, import_react12.useState)(false);
|
|
1409
|
+
const [generateError, setGenerateError] = (0, import_react12.useState)(null);
|
|
1410
|
+
const [result, setResult] = (0, import_react12.useState)(null);
|
|
1411
|
+
const { result: pdfResult, loading: pdfLoading } = usePdfTemplates({ limit: 100 });
|
|
1412
|
+
const { result: htmlResult, loading: htmlLoading } = useHtmlTemplates({ limit: 100 });
|
|
1413
|
+
(0, import_react12.useEffect)(() => {
|
|
1414
|
+
if (step !== "fields" || selectedIds.length === 0) return;
|
|
1415
|
+
const pdfIds = selectedIds.filter((id) => pdfResult?.data.some((t) => t._id === id));
|
|
1416
|
+
const htmlTemplates = htmlResult?.data.filter((t) => selectedIds.includes(t._id)) ?? [];
|
|
1417
|
+
const vars = htmlTemplates.flatMap((t) => t.variables);
|
|
1418
|
+
setHtmlVariables([...new Set(vars)]);
|
|
1419
|
+
if (pdfIds.length === 0) {
|
|
1420
|
+
setFields({});
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
setIntrospecting(true);
|
|
1424
|
+
setIntrospectError(null);
|
|
1425
|
+
client.introspectFields(pdfIds).then((f) => {
|
|
1426
|
+
setFields(f);
|
|
1427
|
+
setIntrospecting(false);
|
|
1428
|
+
}).catch((err) => {
|
|
1429
|
+
setIntrospectError(err.message);
|
|
1430
|
+
setIntrospecting(false);
|
|
1431
|
+
});
|
|
1432
|
+
}, [step, selectedIds, client, pdfResult, htmlResult]);
|
|
1433
|
+
function toggleTemplate(id) {
|
|
1434
|
+
setSelectedIds(
|
|
1435
|
+
(prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
function handleFieldChange(key, value) {
|
|
1439
|
+
setFieldValues((prev) => ({ ...prev, [key]: value }));
|
|
1440
|
+
}
|
|
1441
|
+
async function handleGenerate() {
|
|
1442
|
+
if (!docName.trim()) {
|
|
1443
|
+
setGenerateError("Please enter a document name.");
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
setGenerateError(null);
|
|
1447
|
+
setGenerating(true);
|
|
1448
|
+
try {
|
|
1449
|
+
const templateData = {};
|
|
1450
|
+
for (const [k, v] of Object.entries(fieldValues)) {
|
|
1451
|
+
templateData[k] = v;
|
|
1452
|
+
}
|
|
1453
|
+
const generated = await client.generate({
|
|
1454
|
+
templateIds: selectedIds,
|
|
1455
|
+
data: templateData,
|
|
1456
|
+
name: docName.trim(),
|
|
1457
|
+
description: docDescription.trim() || void 0
|
|
1458
|
+
});
|
|
1459
|
+
setResult(generated);
|
|
1460
|
+
setStep("generate");
|
|
1461
|
+
onSuccess?.(generated);
|
|
1462
|
+
} catch (err) {
|
|
1463
|
+
setGenerateError(err.message);
|
|
1464
|
+
} finally {
|
|
1465
|
+
setGenerating(false);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function reset() {
|
|
1469
|
+
setStep("select");
|
|
1470
|
+
setSelectedIds([]);
|
|
1471
|
+
setFields({});
|
|
1472
|
+
setHtmlVariables([]);
|
|
1473
|
+
setFieldValues({});
|
|
1474
|
+
setDocName("");
|
|
1475
|
+
setDocDescription("");
|
|
1476
|
+
setResult(null);
|
|
1477
|
+
setGenerateError(null);
|
|
1478
|
+
}
|
|
1479
|
+
const allPdfFields = Object.values(fields).flat();
|
|
1480
|
+
const allFieldKeys = [
|
|
1481
|
+
.../* @__PURE__ */ new Set([
|
|
1482
|
+
...allPdfFields.map((f) => f.name),
|
|
1483
|
+
...htmlVariables
|
|
1484
|
+
])
|
|
1485
|
+
];
|
|
1486
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `docman-ui ${className ?? ""}`, children: [
|
|
1487
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "docman-header", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { className: "docman-title", children: "Generate Document" }) }),
|
|
1488
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StepIndicator, { current: step }),
|
|
1489
|
+
step === "select" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
|
|
1490
|
+
(pdfLoading || htmlLoading) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LoadingSpinner, { label: "Loading templates\u2026" }),
|
|
1491
|
+
!pdfLoading && !htmlLoading && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
1492
|
+
(pdfResult?.data.length ?? 0) === 0 && (htmlResult?.data.length ?? 0) === 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "docman-error", children: "No templates found. Upload a PDF template or create an HTML template first." }),
|
|
1493
|
+
(pdfResult?.data.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
1494
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { style: { margin: "0 0 var(--docman-space-sm)" }, children: "PDF Templates" }),
|
|
1495
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "docman-checkbox-grid", style: { marginBottom: "var(--docman-space-lg)" }, children: pdfResult.data.map((t) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1496
|
+
"label",
|
|
1497
|
+
{
|
|
1498
|
+
className: `docman-checkbox-card ${selectedIds.includes(t._id) ? "docman-checkbox-card-selected" : ""}`,
|
|
1499
|
+
style: { cursor: "pointer" },
|
|
1500
|
+
children: [
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1502
|
+
"input",
|
|
1503
|
+
{
|
|
1504
|
+
type: "checkbox",
|
|
1505
|
+
checked: selectedIds.includes(t._id),
|
|
1506
|
+
onChange: () => toggleTemplate(t._id),
|
|
1507
|
+
style: { marginTop: "2px" }
|
|
1508
|
+
}
|
|
1509
|
+
),
|
|
1510
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
|
|
1511
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { fontWeight: 600, fontSize: "0.875rem" }, children: t.name }),
|
|
1512
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { fontSize: "0.8rem", color: "var(--docman-color-text-secondary)" }, children: [
|
|
1513
|
+
t.fields.length,
|
|
1514
|
+
" fields \xB7 ",
|
|
1515
|
+
t.pages,
|
|
1516
|
+
" page",
|
|
1517
|
+
t.pages !== 1 ? "s" : ""
|
|
1518
|
+
] })
|
|
1519
|
+
] })
|
|
1520
|
+
]
|
|
1521
|
+
},
|
|
1522
|
+
t._id
|
|
1523
|
+
)) })
|
|
1524
|
+
] }),
|
|
1525
|
+
(htmlResult?.data.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
1526
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { style: { margin: "0 0 var(--docman-space-sm)" }, children: "HTML Templates" }),
|
|
1527
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "docman-checkbox-grid", style: { marginBottom: "var(--docman-space-lg)" }, children: htmlResult.data.map((t) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1528
|
+
"label",
|
|
1529
|
+
{
|
|
1530
|
+
className: `docman-checkbox-card ${selectedIds.includes(t._id) ? "docman-checkbox-card-selected" : ""}`,
|
|
1531
|
+
style: { cursor: "pointer" },
|
|
1532
|
+
children: [
|
|
1533
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1534
|
+
"input",
|
|
1535
|
+
{
|
|
1536
|
+
type: "checkbox",
|
|
1537
|
+
checked: selectedIds.includes(t._id),
|
|
1538
|
+
onChange: () => toggleTemplate(t._id),
|
|
1539
|
+
style: { marginTop: "2px" }
|
|
1540
|
+
}
|
|
1541
|
+
),
|
|
1542
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
|
|
1543
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { fontWeight: 600, fontSize: "0.875rem" }, children: t.name }),
|
|
1544
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { fontSize: "0.8rem", color: "var(--docman-color-text-secondary)" }, children: [
|
|
1545
|
+
t.variables.length,
|
|
1546
|
+
" variables \xB7 ",
|
|
1547
|
+
t.landscape ? "Landscape" : "Portrait"
|
|
1548
|
+
] })
|
|
1549
|
+
] })
|
|
1550
|
+
]
|
|
1551
|
+
},
|
|
1552
|
+
t._id
|
|
1553
|
+
)) })
|
|
1554
|
+
] }),
|
|
1555
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1556
|
+
"button",
|
|
1557
|
+
{
|
|
1558
|
+
className: "docman-btn docman-btn-primary",
|
|
1559
|
+
onClick: () => setStep("fields"),
|
|
1560
|
+
disabled: selectedIds.length === 0,
|
|
1561
|
+
children: [
|
|
1562
|
+
"Continue with ",
|
|
1563
|
+
selectedIds.length,
|
|
1564
|
+
" template",
|
|
1565
|
+
selectedIds.length !== 1 ? "s" : "",
|
|
1566
|
+
" \u2192"
|
|
1567
|
+
]
|
|
1568
|
+
}
|
|
1569
|
+
) })
|
|
1570
|
+
] })
|
|
1571
|
+
] }),
|
|
1572
|
+
step === "fields" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
|
|
1573
|
+
introspecting && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LoadingSpinner, { label: "Detecting form fields\u2026" }),
|
|
1574
|
+
introspectError && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ErrorAlert, { message: introspectError }),
|
|
1575
|
+
generateError && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ErrorAlert, { message: generateError }),
|
|
1576
|
+
!introspecting && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
1577
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "docman-field", children: [
|
|
1578
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("label", { className: "docman-label", htmlFor: "gen-name", children: "Document name *" }),
|
|
1579
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1580
|
+
"input",
|
|
1581
|
+
{
|
|
1582
|
+
id: "gen-name",
|
|
1583
|
+
className: "docman-input",
|
|
1584
|
+
type: "text",
|
|
1585
|
+
value: docName,
|
|
1586
|
+
onChange: (e) => setDocName(e.target.value),
|
|
1587
|
+
placeholder: "e.g. Quote for Acme Corp"
|
|
1588
|
+
}
|
|
1589
|
+
)
|
|
1590
|
+
] }),
|
|
1591
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "docman-field", children: [
|
|
1592
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("label", { className: "docman-label", htmlFor: "gen-desc", children: "Description" }),
|
|
1593
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1594
|
+
"input",
|
|
1595
|
+
{
|
|
1596
|
+
id: "gen-desc",
|
|
1597
|
+
className: "docman-input",
|
|
1598
|
+
type: "text",
|
|
1599
|
+
value: docDescription,
|
|
1600
|
+
onChange: (e) => setDocDescription(e.target.value),
|
|
1601
|
+
placeholder: "Optional"
|
|
1602
|
+
}
|
|
1603
|
+
)
|
|
1604
|
+
] }),
|
|
1605
|
+
allFieldKeys.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
1606
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { style: { margin: "var(--docman-space-lg) 0 var(--docman-space-sm)" }, children: "Template fields" }),
|
|
1607
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))", gap: "var(--docman-space-md)" }, children: allFieldKeys.map((key) => {
|
|
1608
|
+
const pdfField = allPdfFields.find((f) => f.name === key);
|
|
1609
|
+
const isCheckbox = pdfField?.type === "checkbox";
|
|
1610
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "docman-field", style: { margin: 0 }, children: [
|
|
1611
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "docman-label", htmlFor: `field-${key}`, children: [
|
|
1612
|
+
key,
|
|
1613
|
+
pdfField?.required && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { color: "var(--docman-color-danger)" }, children: " *" })
|
|
1614
|
+
] }),
|
|
1615
|
+
isCheckbox ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1616
|
+
"select",
|
|
1617
|
+
{
|
|
1618
|
+
id: `field-${key}`,
|
|
1619
|
+
className: "docman-select",
|
|
1620
|
+
value: fieldValues[key] ?? "",
|
|
1621
|
+
onChange: (e) => handleFieldChange(key, e.target.value),
|
|
1622
|
+
children: [
|
|
1623
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "", children: "\u2014 select \u2014" }),
|
|
1624
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "Yes", children: "Yes" }),
|
|
1625
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "No", children: "No" })
|
|
1626
|
+
]
|
|
1627
|
+
}
|
|
1628
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1629
|
+
"input",
|
|
1630
|
+
{
|
|
1631
|
+
id: `field-${key}`,
|
|
1632
|
+
className: "docman-input",
|
|
1633
|
+
type: "text",
|
|
1634
|
+
value: fieldValues[key] ?? "",
|
|
1635
|
+
onChange: (e) => handleFieldChange(key, e.target.value),
|
|
1636
|
+
placeholder: key
|
|
1637
|
+
}
|
|
1638
|
+
)
|
|
1639
|
+
] }, key);
|
|
1640
|
+
}) })
|
|
1641
|
+
] }),
|
|
1642
|
+
allFieldKeys.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.875rem", margin: "var(--docman-space-md) 0" }, children: "No form fields detected. Enter a document name and generate." }),
|
|
1643
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { display: "flex", gap: "var(--docman-space-sm)", justifyContent: "flex-end", marginTop: "var(--docman-space-lg)" }, children: [
|
|
1644
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { className: "docman-btn docman-btn-secondary", onClick: () => setStep("select"), disabled: generating, children: "\u2190 Back" }),
|
|
1645
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1646
|
+
"button",
|
|
1647
|
+
{
|
|
1648
|
+
className: "docman-btn docman-btn-primary",
|
|
1649
|
+
onClick: handleGenerate,
|
|
1650
|
+
disabled: generating,
|
|
1651
|
+
children: generating ? "Generating\u2026" : "Generate Document"
|
|
1652
|
+
}
|
|
1653
|
+
)
|
|
1654
|
+
] })
|
|
1655
|
+
] })
|
|
1656
|
+
] }),
|
|
1657
|
+
step === "generate" && result && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "docman-card", style: { textAlign: "center" }, children: [
|
|
1658
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { fontSize: "3rem", marginBottom: "var(--docman-space-sm)" }, children: "\u2705" }),
|
|
1659
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { style: { margin: "0 0 var(--docman-space-sm)" }, children: "Document generated!" }),
|
|
1660
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.875rem", margin: "0 0 var(--docman-space-lg)" }, children: [
|
|
1661
|
+
result.pages,
|
|
1662
|
+
" page",
|
|
1663
|
+
result.pages !== 1 ? "s" : "",
|
|
1664
|
+
" \xB7 ",
|
|
1665
|
+
(result.sizeBytes / 1024).toFixed(1),
|
|
1666
|
+
" KB"
|
|
1667
|
+
] }),
|
|
1668
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { display: "flex", gap: "var(--docman-space-sm)", justifyContent: "center", flexWrap: "wrap" }, children: [
|
|
1669
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1670
|
+
"a",
|
|
1671
|
+
{
|
|
1672
|
+
href: result.downloadUrl,
|
|
1673
|
+
target: "_blank",
|
|
1674
|
+
rel: "noopener noreferrer",
|
|
1675
|
+
className: "docman-btn docman-btn-primary",
|
|
1676
|
+
children: "Download PDF"
|
|
1677
|
+
}
|
|
1678
|
+
),
|
|
1679
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1680
|
+
"a",
|
|
1681
|
+
{
|
|
1682
|
+
href: result.publicUrl,
|
|
1683
|
+
target: "_blank",
|
|
1684
|
+
rel: "noopener noreferrer",
|
|
1685
|
+
className: "docman-btn docman-btn-secondary",
|
|
1686
|
+
children: "Share link"
|
|
1687
|
+
}
|
|
1688
|
+
),
|
|
1689
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { className: "docman-btn docman-btn-secondary", onClick: reset, children: "Generate another" })
|
|
1690
|
+
] })
|
|
1691
|
+
] })
|
|
1692
|
+
] });
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// src/ui/components/OperationsPanel.tsx
|
|
1696
|
+
var import_react13 = require("react");
|
|
1697
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1698
|
+
function toUint82(b) {
|
|
1699
|
+
return b;
|
|
1700
|
+
}
|
|
1701
|
+
function downloadBlob(blob, filename) {
|
|
1702
|
+
const url = URL.createObjectURL(blob);
|
|
1703
|
+
const a = document.createElement("a");
|
|
1704
|
+
a.href = url;
|
|
1705
|
+
a.download = filename;
|
|
1706
|
+
a.click();
|
|
1707
|
+
URL.revokeObjectURL(url);
|
|
1708
|
+
}
|
|
1709
|
+
function MergeTab() {
|
|
1710
|
+
const client = useDocmanClient();
|
|
1711
|
+
const [files, setFiles] = (0, import_react13.useState)([]);
|
|
1712
|
+
const [merging, setMerging] = (0, import_react13.useState)(false);
|
|
1713
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1714
|
+
const inputRef = (0, import_react13.useRef)(null);
|
|
1715
|
+
function handleFilesChange(e) {
|
|
1716
|
+
const selected = Array.from(e.target.files ?? []);
|
|
1717
|
+
setFiles((prev) => [...prev, ...selected]);
|
|
1718
|
+
}
|
|
1719
|
+
function removeFile(index) {
|
|
1720
|
+
setFiles((prev) => prev.filter((_, i) => i !== index));
|
|
1721
|
+
}
|
|
1722
|
+
async function handleMerge() {
|
|
1723
|
+
if (files.length < 2) {
|
|
1724
|
+
setError("Please select at least 2 PDF files to merge.");
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
setError(null);
|
|
1728
|
+
setMerging(true);
|
|
1729
|
+
try {
|
|
1730
|
+
const buffers = await Promise.all(files.map((f) => f.arrayBuffer().then((b) => new Uint8Array(b))));
|
|
1731
|
+
const merged = await client.merge(buffers);
|
|
1732
|
+
downloadBlob(new Blob([toUint82(merged)], { type: "application/pdf" }), "merged.pdf");
|
|
1733
|
+
setFiles([]);
|
|
1734
|
+
} catch (err) {
|
|
1735
|
+
setError(err.message);
|
|
1736
|
+
} finally {
|
|
1737
|
+
setMerging(false);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
|
|
1741
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.875rem", marginTop: 0 }, children: "Combine multiple PDFs into a single file in the order listed below." }),
|
|
1742
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ErrorAlert, { message: error }),
|
|
1743
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1744
|
+
"div",
|
|
1745
|
+
{
|
|
1746
|
+
className: "docman-dropzone",
|
|
1747
|
+
onClick: () => inputRef.current?.click(),
|
|
1748
|
+
role: "button",
|
|
1749
|
+
tabIndex: 0,
|
|
1750
|
+
onKeyDown: (e) => e.key === "Enter" && inputRef.current?.click(),
|
|
1751
|
+
"aria-label": "Click to add PDF files",
|
|
1752
|
+
children: [
|
|
1753
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { children: "+ Add PDF files" }),
|
|
1754
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1755
|
+
"input",
|
|
1756
|
+
{
|
|
1757
|
+
ref: inputRef,
|
|
1758
|
+
type: "file",
|
|
1759
|
+
accept: "application/pdf",
|
|
1760
|
+
multiple: true,
|
|
1761
|
+
style: { display: "none" },
|
|
1762
|
+
onChange: handleFilesChange
|
|
1763
|
+
}
|
|
1764
|
+
)
|
|
1765
|
+
]
|
|
1766
|
+
}
|
|
1767
|
+
),
|
|
1768
|
+
files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { marginTop: "var(--docman-space-md)" }, children: files.map((f, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1769
|
+
"div",
|
|
1770
|
+
{
|
|
1771
|
+
style: {
|
|
1772
|
+
display: "flex",
|
|
1773
|
+
alignItems: "center",
|
|
1774
|
+
justifyContent: "space-between",
|
|
1775
|
+
padding: "var(--docman-space-sm) var(--docman-space-md)",
|
|
1776
|
+
border: "1px solid var(--docman-color-border)",
|
|
1777
|
+
borderRadius: "var(--docman-radius-md)",
|
|
1778
|
+
marginBottom: "var(--docman-space-sm)",
|
|
1779
|
+
background: "var(--docman-color-surface)"
|
|
1780
|
+
},
|
|
1781
|
+
children: [
|
|
1782
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { style: { fontSize: "0.875rem" }, children: [
|
|
1783
|
+
i + 1,
|
|
1784
|
+
". ",
|
|
1785
|
+
f.name
|
|
1786
|
+
] }),
|
|
1787
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { className: "docman-btn docman-btn-danger docman-btn-sm", onClick: () => removeFile(i), children: "Remove" })
|
|
1788
|
+
]
|
|
1789
|
+
},
|
|
1790
|
+
i
|
|
1791
|
+
)) }),
|
|
1792
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { marginTop: "var(--docman-space-lg)", display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1793
|
+
"button",
|
|
1794
|
+
{
|
|
1795
|
+
className: "docman-btn docman-btn-primary",
|
|
1796
|
+
onClick: handleMerge,
|
|
1797
|
+
disabled: merging || files.length < 2,
|
|
1798
|
+
children: merging ? "Merging\u2026" : `Merge ${files.length} files`
|
|
1799
|
+
}
|
|
1800
|
+
) })
|
|
1801
|
+
] });
|
|
1802
|
+
}
|
|
1803
|
+
function SplitTab() {
|
|
1804
|
+
const client = useDocmanClient();
|
|
1805
|
+
const [file, setFile] = (0, import_react13.useState)(null);
|
|
1806
|
+
const [pages, setPages] = (0, import_react13.useState)("");
|
|
1807
|
+
const [splitting, setSplitting] = (0, import_react13.useState)(false);
|
|
1808
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1809
|
+
const inputRef = (0, import_react13.useRef)(null);
|
|
1810
|
+
async function handleSplit() {
|
|
1811
|
+
if (!file) {
|
|
1812
|
+
setError("Please select a PDF file.");
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
if (!pages.trim()) {
|
|
1816
|
+
setError('Please enter a page selection (e.g. "1-3,5,7-9").');
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
setError(null);
|
|
1820
|
+
setSplitting(true);
|
|
1821
|
+
try {
|
|
1822
|
+
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
1823
|
+
const ranges = pages.split(",").map((part) => {
|
|
1824
|
+
const [start, end] = part.trim().split("-").map(Number);
|
|
1825
|
+
return { start, end: end ?? start };
|
|
1826
|
+
});
|
|
1827
|
+
const parts = await client.split(buffer, ranges);
|
|
1828
|
+
for (const part of parts) {
|
|
1829
|
+
const name = parts.length === 1 ? `split-p${part.range.start}-${part.range.end}.pdf` : `split-part-${part.index + 1}.pdf`;
|
|
1830
|
+
downloadBlob(new Blob([toUint82(part.buffer)], { type: "application/pdf" }), name);
|
|
1831
|
+
}
|
|
1832
|
+
setPages("");
|
|
1833
|
+
setFile(null);
|
|
1834
|
+
} catch (err) {
|
|
1835
|
+
setError(err.message);
|
|
1836
|
+
} finally {
|
|
1837
|
+
setSplitting(false);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
|
|
1841
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { style: { color: "var(--docman-color-text-secondary)", fontSize: "0.875rem", marginTop: 0 }, children: [
|
|
1842
|
+
"Extract pages from a PDF. Enter page ranges like ",
|
|
1843
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { children: "1-3,5,7-9" }),
|
|
1844
|
+
"."
|
|
1845
|
+
] }),
|
|
1846
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ErrorAlert, { message: error }),
|
|
1847
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1848
|
+
"div",
|
|
1849
|
+
{
|
|
1850
|
+
className: "docman-dropzone",
|
|
1851
|
+
onClick: () => inputRef.current?.click(),
|
|
1852
|
+
role: "button",
|
|
1853
|
+
tabIndex: 0,
|
|
1854
|
+
onKeyDown: (e) => e.key === "Enter" && inputRef.current?.click(),
|
|
1855
|
+
"aria-label": "Click to select a PDF file",
|
|
1856
|
+
children: [
|
|
1857
|
+
file ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { fontWeight: 500 }, children: file.name }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { children: "Click to select a PDF" }),
|
|
1858
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1859
|
+
"input",
|
|
1860
|
+
{
|
|
1861
|
+
ref: inputRef,
|
|
1862
|
+
type: "file",
|
|
1863
|
+
accept: "application/pdf",
|
|
1864
|
+
style: { display: "none" },
|
|
1865
|
+
onChange: (e) => setFile(e.target.files?.[0] ?? null)
|
|
1866
|
+
}
|
|
1867
|
+
)
|
|
1868
|
+
]
|
|
1869
|
+
}
|
|
1870
|
+
),
|
|
1871
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "docman-field", style: { marginTop: "var(--docman-space-md)" }, children: [
|
|
1872
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("label", { className: "docman-label", htmlFor: "split-pages", children: "Page selection" }),
|
|
1873
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1874
|
+
"input",
|
|
1875
|
+
{
|
|
1876
|
+
id: "split-pages",
|
|
1877
|
+
className: "docman-input",
|
|
1878
|
+
type: "text",
|
|
1879
|
+
value: pages,
|
|
1880
|
+
onChange: (e) => setPages(e.target.value),
|
|
1881
|
+
placeholder: "e.g. 1-3,5,7-9"
|
|
1882
|
+
}
|
|
1883
|
+
)
|
|
1884
|
+
] }),
|
|
1885
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1886
|
+
"button",
|
|
1887
|
+
{
|
|
1888
|
+
className: "docman-btn docman-btn-primary",
|
|
1889
|
+
onClick: handleSplit,
|
|
1890
|
+
disabled: splitting || !file,
|
|
1891
|
+
children: splitting ? "Splitting\u2026" : "Split & Download"
|
|
1892
|
+
}
|
|
1893
|
+
) })
|
|
1894
|
+
] });
|
|
1895
|
+
}
|
|
1896
|
+
function OperationsPanel({ className }) {
|
|
1897
|
+
const [tab, setTab] = (0, import_react13.useState)("merge");
|
|
1898
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `docman-ui ${className ?? ""}`, children: [
|
|
1899
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "docman-header", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "docman-title", children: "PDF Operations" }) }),
|
|
1900
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "docman-tabs", children: [
|
|
1901
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1902
|
+
"button",
|
|
1903
|
+
{
|
|
1904
|
+
className: `docman-tab ${tab === "merge" ? "docman-tab-active" : ""}`,
|
|
1905
|
+
onClick: () => setTab("merge"),
|
|
1906
|
+
children: "Merge"
|
|
1907
|
+
}
|
|
1908
|
+
),
|
|
1909
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1910
|
+
"button",
|
|
1911
|
+
{
|
|
1912
|
+
className: `docman-tab ${tab === "split" ? "docman-tab-active" : ""}`,
|
|
1913
|
+
onClick: () => setTab("split"),
|
|
1914
|
+
children: "Split"
|
|
1915
|
+
}
|
|
1916
|
+
)
|
|
1917
|
+
] }),
|
|
1918
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "docman-card", children: tab === "merge" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MergeTab, {}) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SplitTab, {}) })
|
|
1919
|
+
] });
|
|
1920
|
+
}
|
|
1921
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1922
|
+
0 && (module.exports = {
|
|
1923
|
+
DocmanProvider,
|
|
1924
|
+
DocumentGenerator,
|
|
1925
|
+
DocumentList,
|
|
1926
|
+
HtmlTemplateEditor,
|
|
1927
|
+
HtmlTemplateList,
|
|
1928
|
+
OperationsPanel,
|
|
1929
|
+
PdfTemplateList,
|
|
1930
|
+
PdfTemplateUploader,
|
|
1931
|
+
TemplateGroupList,
|
|
1932
|
+
useDocmanClient,
|
|
1933
|
+
useDocuments,
|
|
1934
|
+
useHtmlTemplates,
|
|
1935
|
+
usePdfTemplates,
|
|
1936
|
+
useTemplateGroups
|
|
1937
|
+
});
|
|
1938
|
+
//# sourceMappingURL=index.cjs.map
|