@fayz-ai/plugin-forms 0.13.5 → 0.14.0

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.
Files changed (53) hide show
  1. package/dist/{FormBuilder-AO6V2YOT.js → FormBuilder-N4R5SENC.js} +161 -15
  2. package/dist/FormBuilder-N4R5SENC.js.map +1 -0
  3. package/dist/chunk-2DFNG5UT.js +1661 -0
  4. package/dist/chunk-2DFNG5UT.js.map +1 -0
  5. package/dist/components/ContractSignPane.d.ts +12 -0
  6. package/dist/components/ContractSignPane.d.ts.map +1 -0
  7. package/dist/components/DocumentPreview.d.ts.map +1 -1
  8. package/dist/components/FieldConfigDialog.d.ts.map +1 -1
  9. package/dist/components/FieldPalette.d.ts.map +1 -1
  10. package/dist/components/FormFillDialog.d.ts +22 -2
  11. package/dist/components/FormFillDialog.d.ts.map +1 -1
  12. package/dist/components/FormRenderer.d.ts +9 -2
  13. package/dist/components/FormRenderer.d.ts.map +1 -1
  14. package/dist/components/PersonAnswerAlerts.d.ts +12 -0
  15. package/dist/components/PersonAnswerAlerts.d.ts.map +1 -0
  16. package/dist/components/PersonFormsAlert.d.ts +6 -1
  17. package/dist/components/PersonFormsAlert.d.ts.map +1 -1
  18. package/dist/components/fields/FieldTypeSettings.d.ts +11 -0
  19. package/dist/components/fields/FieldTypeSettings.d.ts.map +1 -0
  20. package/dist/components/fields/StencilCamera.d.ts +19 -0
  21. package/dist/components/fields/StencilCamera.d.ts.map +1 -0
  22. package/dist/components/fields/fields.d.ts +59 -0
  23. package/dist/components/fields/fields.d.ts.map +1 -0
  24. package/dist/components/fields/media.d.ts +18 -0
  25. package/dist/components/fields/media.d.ts.map +1 -0
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/context.d.ts +2 -0
  28. package/dist/context.d.ts.map +1 -1
  29. package/dist/data/mock.d.ts.map +1 -1
  30. package/dist/data/supabase.d.ts.map +1 -1
  31. package/dist/data/types.d.ts +27 -0
  32. package/dist/data/types.d.ts.map +1 -1
  33. package/dist/index.d.ts +5 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +969 -134
  36. package/dist/index.js.map +1 -1
  37. package/dist/lib/attendance-contracts.d.ts +61 -0
  38. package/dist/lib/attendance-contracts.d.ts.map +1 -0
  39. package/dist/lib/categories.d.ts +1 -1
  40. package/dist/lib/categories.d.ts.map +1 -1
  41. package/dist/lib/contract-render.d.ts +57 -0
  42. package/dist/lib/contract-render.d.ts.map +1 -0
  43. package/dist/lib/print.d.ts.map +1 -1
  44. package/dist/locales/en.d.ts.map +1 -1
  45. package/dist/locales/pt-BR.d.ts.map +1 -1
  46. package/dist/types.d.ts +52 -3
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -1
  49. package/dist/views/TemplateListView.d.ts.map +1 -1
  50. package/package.json +4 -4
  51. package/dist/FormBuilder-AO6V2YOT.js.map +0 -1
  52. package/dist/chunk-2AIKIPGU.js +0 -695
  53. package/dist/chunk-2AIKIPGU.js.map +0 -1
@@ -1,695 +0,0 @@
1
- import { useCallback } from 'react';
2
- import { Badge, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Input } from '@fayz-ai/ui';
3
- import { useTranslation } from '@fayz-ai/core';
4
- import { jsx, jsxs } from 'react/jsx-runtime';
5
-
6
- // src/components/FormRenderer.tsx
7
-
8
- // src/lib/html-text.ts
9
- function looksLikeHtml(value) {
10
- return /<\/?[a-z][^>]*>/i.test(value);
11
- }
12
- var BLOCKS = "p, div, li, tr, h1, h2, h3, h4, h5, h6, blockquote, section, article";
13
- var NBSP = / /g;
14
- function htmlToPlainText(value) {
15
- if (!value) return "";
16
- if (typeof document === "undefined") {
17
- return value.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|li|tr|h[1-6]|blockquote)>/gi, "\n").replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&").replace(/\n{3,}/g, "\n\n").trim();
18
- }
19
- const el = document.createElement("div");
20
- el.innerHTML = value;
21
- for (const br of Array.from(el.querySelectorAll("br"))) {
22
- br.replaceWith("\n");
23
- }
24
- for (const block of Array.from(el.querySelectorAll(BLOCKS))) {
25
- block.append("\n");
26
- }
27
- return (el.textContent ?? "").replace(NBSP, " ").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
28
- }
29
- var DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
30
- "script",
31
- "style",
32
- "iframe",
33
- "object",
34
- "embed",
35
- "svg",
36
- "math",
37
- "link",
38
- "meta",
39
- "form",
40
- "input",
41
- "button",
42
- "textarea",
43
- "select",
44
- "template",
45
- "noscript"
46
- ]);
47
- var ALLOWED_TAGS = /* @__PURE__ */ new Set([
48
- "p",
49
- "br",
50
- "hr",
51
- "div",
52
- "span",
53
- "b",
54
- "strong",
55
- "i",
56
- "em",
57
- "u",
58
- "s",
59
- "sub",
60
- "sup",
61
- "mark",
62
- "small",
63
- "ul",
64
- "ol",
65
- "li",
66
- "blockquote",
67
- "pre",
68
- "code",
69
- "h1",
70
- "h2",
71
- "h3",
72
- "h4",
73
- "h5",
74
- "h6",
75
- "table",
76
- "thead",
77
- "tbody",
78
- "tfoot",
79
- "tr",
80
- "td",
81
- "th",
82
- "caption",
83
- "a"
84
- ]);
85
- var ALLOWED_ATTRS = {
86
- a: /* @__PURE__ */ new Set(["href", "title"]),
87
- td: /* @__PURE__ */ new Set(["colspan", "rowspan"]),
88
- th: /* @__PURE__ */ new Set(["colspan", "rowspan"])
89
- };
90
- var SAFE_HREF = /^(https?:|mailto:|tel:)/i;
91
- function cleanElement(el) {
92
- for (const child of Array.from(el.children)) {
93
- cleanElement(child);
94
- }
95
- const tag = el.tagName.toLowerCase();
96
- if (DROP_WITH_CONTENT.has(tag)) {
97
- el.remove();
98
- return;
99
- }
100
- for (const attr of Array.from(el.attributes)) {
101
- const name = attr.name.toLowerCase();
102
- const allowed = ALLOWED_ATTRS[tag]?.has(name) ?? false;
103
- if (!allowed) {
104
- el.removeAttribute(attr.name);
105
- continue;
106
- }
107
- if (name === "href" && !SAFE_HREF.test(attr.value.trim())) {
108
- el.removeAttribute(attr.name);
109
- }
110
- }
111
- if (!ALLOWED_TAGS.has(tag)) {
112
- el.replaceWith(...Array.from(el.childNodes));
113
- return;
114
- }
115
- if (tag === "a") {
116
- if (!el.getAttribute("href")) {
117
- el.replaceWith(...Array.from(el.childNodes));
118
- return;
119
- }
120
- el.setAttribute("target", "_blank");
121
- el.setAttribute("rel", "noopener noreferrer nofollow");
122
- }
123
- }
124
- function sanitizeHtml(value) {
125
- if (!value) return "";
126
- if (typeof DOMParser === "undefined") return "";
127
- const doc = new DOMParser().parseFromString(value, "text/html");
128
- for (const child of Array.from(doc.body.children)) {
129
- cleanElement(child);
130
- }
131
- return doc.body.innerHTML;
132
- }
133
- function fieldRichValue(value) {
134
- if (typeof value !== "string" || !value) return { text: "" };
135
- if (!looksLikeHtml(value)) return { text: value };
136
- const html = sanitizeHtml(value);
137
- return html ? { html } : { text: htmlToPlainText(value) };
138
- }
139
-
140
- // src/lib/plain-text.ts
141
- var ENTITIES = {
142
- amp: "&",
143
- lt: "<",
144
- gt: ">",
145
- quot: '"',
146
- apos: "'",
147
- nbsp: " ",
148
- // Os acentos do português, que é o que 23 moldes do Feegow trouxeram. Escrito
149
- // à mão e não resolvido pelo DOM porque esta função também roda em teste e
150
- // fora do navegador — e um `&ccedil;` visível na anamnese é o defeito que
151
- // ela veio consertar.
152
- ...Object.fromEntries(
153
- "agrave \xE0 aacute \xE1 acirc \xE2 atilde \xE3 auml \xE4 ccedil \xE7 egrave \xE8 eacute \xE9 ecirc \xEA euml \xEB igrave \xEC iacute \xED icirc \xEE iuml \xEF ntilde \xF1 ograve \xF2 oacute \xF3 ocirc \xF4 otilde \xF5 ouml \xF6 ugrave \xF9 uacute \xFA ucirc \xFB uuml \xFC".split(" ").reduce((acc, part, i, all) => {
154
- if (i % 2 === 0) acc.push([part, all[i + 1]]);
155
- return acc;
156
- }, []).flatMap(([name, char]) => [
157
- [name, char],
158
- [name.charAt(0).toUpperCase() + name.slice(1), char.toUpperCase()]
159
- ])
160
- )
161
- };
162
- function decodeEntities(value) {
163
- return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (whole, body) => {
164
- if (body[0] === "#") {
165
- const code = body[1] === "x" || body[1] === "X" ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10);
166
- return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
167
- }
168
- const named = ENTITIES[body] ?? ENTITIES[body.toLowerCase()];
169
- if (named) return named;
170
- if (typeof document === "undefined") return whole;
171
- const el = document.createElement("textarea");
172
- el.innerHTML = whole;
173
- return el.value;
174
- });
175
- }
176
- function htmlToPlainText2(value) {
177
- if (!value || !/[<&]/.test(value)) return value;
178
- const text = decodeEntities(
179
- value.replace(/<\s*br\s*\/?\s*>/gi, "\n").replace(/<\s*\/\s*(p|div|li|h[1-6])\s*>/gi, "\n").replace(/<[^>]*>/g, "")
180
- );
181
- return text.split("\n").map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n") || void 0;
182
- }
183
- var RICH_TEXT_CLASS = [
184
- "space-y-2",
185
- "[&_p]:leading-relaxed",
186
- "[&_b]:font-semibold [&_strong]:font-semibold",
187
- "[&_i]:italic [&_em]:italic",
188
- "[&_u]:underline",
189
- "[&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_li]:leading-relaxed",
190
- "[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground",
191
- "[&_a]:text-primary [&_a]:underline",
192
- "[&_h1]:font-semibold [&_h2]:font-semibold [&_h3]:font-semibold",
193
- "[&_table]:w-full [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1",
194
- "[&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:font-medium"
195
- ].join(" ");
196
- function ReadField({ label, children }) {
197
- return /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
198
- label && /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: label }),
199
- /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground", children })
200
- ] });
201
- }
202
- function EmptyValue() {
203
- return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/50", children: "\u2014" });
204
- }
205
- function optionLabel(field, value) {
206
- if (value == null || value === "") return null;
207
- const match = (field.options ?? []).find((o) => o.value === value);
208
- return match?.label ?? String(value);
209
- }
210
- function ReadValue({ field, value }) {
211
- switch (field.type) {
212
- case "memo":
213
- case "richtext": {
214
- const rich = fieldRichValue(value);
215
- return /* @__PURE__ */ jsx(ReadField, { label: field.label, children: "html" in rich ? /* @__PURE__ */ jsx("div", { className: RICH_TEXT_CLASS, dangerouslySetInnerHTML: { __html: rich.html } }) : rich.text ? /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: rich.text }) : /* @__PURE__ */ jsx(EmptyValue, {}) });
216
- }
217
- case "date": {
218
- const raw = typeof value === "string" ? value : "";
219
- const date = raw ? new Date(raw) : null;
220
- const text = date && !Number.isNaN(date.getTime()) ? raw.slice(0, 10).split("-").reverse().join("/") : raw;
221
- return /* @__PURE__ */ jsx(ReadField, { label: field.label, children: text || /* @__PURE__ */ jsx(EmptyValue, {}) });
222
- }
223
- case "select":
224
- case "radio": {
225
- const label = optionLabel(field, value);
226
- return /* @__PURE__ */ jsx(ReadField, { label: field.label, children: label ?? /* @__PURE__ */ jsx(EmptyValue, {}) });
227
- }
228
- case "tags": {
229
- const selected = Array.isArray(value) ? value : [];
230
- return /* @__PURE__ */ jsx(ReadField, { label: field.label, children: selected.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: selected.map((v) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: optionLabel(field, v) ?? v }, v)) }) : /* @__PURE__ */ jsx(EmptyValue, {}) });
231
- }
232
- case "checkbox":
233
- return /* @__PURE__ */ jsxs(ReadField, { children: [
234
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: field.label }),
235
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: " \xB7 " }),
236
- /* @__PURE__ */ jsx("span", { children: value ? "Sim" : "N\xE3o" })
237
- ] });
238
- default: {
239
- const text = value == null || value === "" ? null : String(value);
240
- return /* @__PURE__ */ jsx(ReadField, { label: field.label, children: text ?? /* @__PURE__ */ jsx(EmptyValue, {}) });
241
- }
242
- }
243
- }
244
- function FieldRenderer({
245
- field,
246
- value,
247
- onChange,
248
- readOnly
249
- }) {
250
- useTranslation();
251
- if (readOnly && field.type !== "title" && field.type !== "image" && field.type !== "gallery" && field.type !== "budget") {
252
- return /* @__PURE__ */ jsx(ReadValue, { field, value });
253
- }
254
- switch (field.type) {
255
- case "title":
256
- return /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold pt-2 pb-1 border-b", children: field.label });
257
- case "text":
258
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
259
- /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium block", children: [
260
- field.label,
261
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
262
- ] }),
263
- /* @__PURE__ */ jsx(
264
- Input,
265
- {
266
- value: value ?? "",
267
- onChange: (e) => onChange(e.target.value),
268
- placeholder: htmlToPlainText2(field.placeholder),
269
- className: "h-9 text-sm",
270
- readOnly
271
- }
272
- )
273
- ] });
274
- case "memo":
275
- case "richtext":
276
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
277
- /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium block", children: [
278
- field.label,
279
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
280
- ] }),
281
- /* @__PURE__ */ jsx(
282
- "textarea",
283
- {
284
- value: value ?? "",
285
- onChange: (e) => onChange(e.target.value),
286
- placeholder: htmlToPlainText2(field.placeholder),
287
- className: "flex w-full rounded-input border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 min-h-[100px]",
288
- readOnly
289
- }
290
- )
291
- ] });
292
- case "date":
293
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
294
- /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium block", children: [
295
- field.label,
296
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
297
- ] }),
298
- /* @__PURE__ */ jsx(
299
- Input,
300
- {
301
- type: "date",
302
- value: value ?? "",
303
- onChange: (e) => onChange(e.target.value),
304
- className: "h-9 text-sm",
305
- readOnly
306
- }
307
- )
308
- ] });
309
- case "select":
310
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
311
- /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium block", children: [
312
- field.label,
313
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
314
- ] }),
315
- readOnly ? /* @__PURE__ */ jsx("p", { className: "text-sm py-1", children: value ?? "-" }) : /* @__PURE__ */ jsxs(Select, { value: value ?? "", onValueChange: onChange, children: [
316
- /* @__PURE__ */ jsx(SelectTrigger, { className: "h-9 text-sm", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: htmlToPlainText2(field.placeholder) ?? "..." }) }),
317
- /* @__PURE__ */ jsx(SelectContent, { children: (field.options ?? []).map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
318
- ] })
319
- ] });
320
- case "radio":
321
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
322
- /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium block", children: [
323
- field.label,
324
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
325
- ] }),
326
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: (field.options ?? []).map((opt) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
327
- /* @__PURE__ */ jsx(
328
- "input",
329
- {
330
- type: "radio",
331
- name: field.id,
332
- value: opt.value,
333
- checked: value === opt.value,
334
- onChange: () => onChange(opt.value),
335
- disabled: readOnly,
336
- className: "text-primary"
337
- }
338
- ),
339
- /* @__PURE__ */ jsx("span", { className: "text-sm", children: opt.label })
340
- ] }, opt.value)) })
341
- ] });
342
- case "tags": {
343
- const selectedTags = Array.isArray(value) ? value : [];
344
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
345
- /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium block", children: [
346
- field.label,
347
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
348
- ] }),
349
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: (field.options ?? []).map((opt) => {
350
- const active = selectedTags.includes(opt.value);
351
- return /* @__PURE__ */ jsx(
352
- Badge,
353
- {
354
- variant: active ? "default" : "outline",
355
- className: `cursor-pointer text-xs ${readOnly ? "pointer-events-none" : ""}`,
356
- onClick: () => {
357
- if (readOnly) return;
358
- onChange(
359
- active ? selectedTags.filter((t2) => t2 !== opt.value) : [...selectedTags, opt.value]
360
- );
361
- },
362
- children: opt.label
363
- },
364
- opt.value
365
- );
366
- }) })
367
- ] });
368
- }
369
- case "checkbox":
370
- return /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer py-1", children: [
371
- /* @__PURE__ */ jsx(
372
- "input",
373
- {
374
- type: "checkbox",
375
- checked: !!value,
376
- onChange: (e) => onChange(e.target.checked),
377
- disabled: readOnly,
378
- className: "rounded border-input"
379
- }
380
- ),
381
- /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
382
- field.label,
383
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
384
- ] })
385
- ] });
386
- case "image":
387
- case "gallery":
388
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
389
- /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium block", children: [
390
- field.label,
391
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
392
- ] }),
393
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center rounded-lg border-2 border-dashed py-8 text-sm text-muted-foreground", children: [
394
- field.type === "gallery" ? "Gallery upload" : "Image upload",
395
- " \u2014 v2"
396
- ] })
397
- ] });
398
- case "budget":
399
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
400
- /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium block", children: [
401
- field.label,
402
- field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
403
- ] }),
404
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center rounded-lg border-2 border-dashed py-8 text-sm text-muted-foreground", children: "Budget / line items \u2014 v2" })
405
- ] });
406
- default:
407
- return null;
408
- }
409
- }
410
- function FormRenderer({ schema, data, onChange, readOnly }) {
411
- const sortedFields = [...schema.fields].sort((a, b) => a.row - b.row || a.col - b.col);
412
- const handleFieldChange = useCallback(
413
- (fieldId, value) => {
414
- onChange({ ...data, [fieldId]: value });
415
- },
416
- [data, onChange]
417
- );
418
- return /* @__PURE__ */ jsx(
419
- "div",
420
- {
421
- className: "grid gap-x-4 gap-y-3",
422
- style: { gridTemplateColumns: "repeat(12, 1fr)" },
423
- children: sortedFields.map((field) => /* @__PURE__ */ jsx("div", { style: { gridColumn: `span ${field.colSpan}` }, children: /* @__PURE__ */ jsx(
424
- FieldRenderer,
425
- {
426
- field,
427
- value: data[field.id],
428
- onChange: (v) => handleFieldChange(field.id, v),
429
- readOnly
430
- }
431
- ) }, field.id))
432
- }
433
- );
434
- }
435
-
436
- // src/lib/print.ts
437
- function esc(value) {
438
- return String(value ?? "").replace(
439
- /[&<>"']/g,
440
- (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]
441
- );
442
- }
443
- function nl2br(value) {
444
- return esc(value).replace(/\r?\n/g, "<br/>");
445
- }
446
- function formatDate(value) {
447
- const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
448
- if (!m) return esc(value);
449
- return `${m[3]}/${m[2]}/${m[1]}`;
450
- }
451
- function optionLabel2(field, value) {
452
- const opt = (field.options ?? []).find((o) => o.value === value);
453
- return esc(opt ? opt.label : value);
454
- }
455
- function renderValue(field, value) {
456
- const empty = '<span class="empty">\u2014</span>';
457
- switch (field.type) {
458
- case "memo":
459
- case "richtext": {
460
- const rich = fieldRichValue(value);
461
- if ("html" in rich) return `<div class="long rich">${rich.html}</div>`;
462
- return rich.text ? `<div class="long">${nl2br(rich.text)}</div>` : empty;
463
- }
464
- case "date":
465
- return value ? formatDate(String(value)) : empty;
466
- case "select":
467
- case "radio":
468
- return value ? optionLabel2(field, value) : empty;
469
- case "tags": {
470
- const arr = Array.isArray(value) ? value : [];
471
- return arr.length ? arr.map((v) => optionLabel2(field, v)).join(", ") : empty;
472
- }
473
- case "checkbox":
474
- return value ? "Sim" : "N\xE3o";
475
- case "image":
476
- case "gallery":
477
- case "budget":
478
- return "";
479
- // not meaningful on a printed contract
480
- default:
481
- return value ? esc(value) : empty;
482
- }
483
- }
484
- function buildPrintHtml(template, doc, opts = {}) {
485
- const heading = opts.heading ?? doc.title ?? template.name;
486
- const subParts = [opts.subheading ?? doc.personName, new Date(doc.createdAt).toLocaleDateString()].filter(Boolean).map((s) => esc(s)).join(" &middot; ");
487
- const fields = [...template.schema.fields].sort((a, b) => a.row - b.row || a.col - b.col);
488
- const clampSpan = (n) => Math.max(1, Math.min(12, Number(n) || 12));
489
- const body = fields.map((field) => {
490
- if (field.type === "title") {
491
- return `<h2 class="section" style="grid-column: 1 / -1">${esc(field.label)}</h2>`;
492
- }
493
- const rendered = renderValue(field, doc.data[field.id]);
494
- if (rendered === "") return "";
495
- const isLong = field.type === "memo" || field.type === "richtext";
496
- return `<div class="field${isLong ? " field--long" : ""}" style="grid-column: span ${clampSpan(field.colSpan)}">
497
- <div class="label">${esc(field.label)}</div>
498
- <div class="value">${rendered}</div>
499
- </div>`;
500
- }).join("\n");
501
- return `<!doctype html>
502
- <html lang="pt-BR">
503
- <head>
504
- <meta charset="utf-8"/>
505
- <title>${esc(heading)}</title>
506
- <style>
507
- @page { size: A4; margin: 20mm; }
508
- * { box-sizing: border-box; }
509
- html, body { margin: 0; padding: 0; }
510
- body {
511
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
512
- color: #111; font-size: 12pt; line-height: 1.5;
513
- -webkit-print-color-adjust: exact; print-color-adjust: exact;
514
- }
515
- .page { max-width: 170mm; margin: 0 auto; padding: 12mm 0; }
516
- header { border-bottom: 2px solid #111; padding-bottom: 8px; margin-bottom: 20px; }
517
- header h1 { font-size: 18pt; margin: 0 0 2px; }
518
- header .sub { font-size: 10pt; color: #555; }
519
- /* 12-column grid \u2014 mirrors the builder Preview (FormRenderer) so the print
520
- keeps the authored column layout. */
521
- .grid { display: grid; grid-template-columns: repeat(12, 1fr); column-gap: 16px; row-gap: 12px; align-items: start; }
522
- h2.section { font-size: 13pt; margin: 22px 0 4px; padding-bottom: 3px; border-bottom: 1px solid #ccc; }
523
- .grid > h2.section:first-child { margin-top: 4px; }
524
- .field { margin: 0; min-width: 0; }
525
- .field .label { font-size: 9pt; text-transform: uppercase; letter-spacing: .04em; color: #666; margin-bottom: 2px; }
526
- .field .value { font-size: 12pt; }
527
- .field--long .value .long { white-space: normal; text-align: justify; }
528
- /* Conteudo formatado (sanitizado em lib/html-text.ts). O HTML chega sem
529
- class nenhuma, entao o estilo vem por tag. orphans/widows evitam que um
530
- paragrafo deixe uma linha solta virando a pagina. */
531
- .long.rich p { margin: 0 0 8px; orphans: 2; widows: 2; }
532
- .long.rich p:last-child { margin-bottom: 0; }
533
- .long.rich ul, .long.rich ol { margin: 0 0 8px; padding-left: 20px; }
534
- .long.rich li { margin: 0 0 3px; }
535
- .long.rich b, .long.rich strong { font-weight: 600; }
536
- .long.rich blockquote { margin: 0 0 8px; padding-left: 10px; border-left: 2px solid #ccc; color: #444; }
537
- .long.rich a { color: #111; text-decoration: underline; }
538
- .long.rich table { width: 100%; border-collapse: collapse; margin: 0 0 8px; }
539
- .long.rich td, .long.rich th { border: 1px solid #ccc; padding: 4px 6px; text-align: left; }
540
- .long.rich h1, .long.rich h2, .long.rich h3,
541
- .long.rich h4, .long.rich h5, .long.rich h6 { font-size: 12pt; font-weight: 600; margin: 10px 0 4px; }
542
- .empty { color: #bbb; }
543
- footer { margin-top: 40px; font-size: 9pt; color: #888; text-align: center; }
544
- @media screen { body { background: #f3f3f3; } .page { background: #fff; max-width: 210mm; min-height: 297mm; padding: 20mm; box-shadow: 0 1px 8px rgba(0,0,0,.15); } }
545
- </style>
546
- </head>
547
- <body>
548
- <div class="page">
549
- <header>
550
- <h1>${esc(heading)}</h1>
551
- ${subParts ? `<div class="sub">${subParts}</div>` : ""}
552
- </header>
553
- <div class="grid">
554
- ${body}
555
- </div>
556
- </div>
557
- </body>
558
- </html>`;
559
- }
560
- function printHtml(html) {
561
- if (typeof document === "undefined") return;
562
- const iframe = document.createElement("iframe");
563
- iframe.setAttribute("aria-hidden", "true");
564
- iframe.style.position = "fixed";
565
- iframe.style.right = "0";
566
- iframe.style.bottom = "0";
567
- iframe.style.width = "0";
568
- iframe.style.height = "0";
569
- iframe.style.border = "0";
570
- document.body.appendChild(iframe);
571
- const cleanup = () => {
572
- setTimeout(() => {
573
- if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
574
- }, 1e3);
575
- };
576
- const win = iframe.contentWindow;
577
- const idoc = iframe.contentDocument || win?.document;
578
- if (!win || !idoc) {
579
- cleanup();
580
- return;
581
- }
582
- idoc.open();
583
- idoc.write(html);
584
- idoc.close();
585
- const doPrint = () => {
586
- try {
587
- win.focus();
588
- win.print();
589
- } finally {
590
- cleanup();
591
- }
592
- };
593
- if (idoc.readyState === "complete") {
594
- setTimeout(doPrint, 50);
595
- } else {
596
- iframe.addEventListener("load", () => setTimeout(doPrint, 50), { once: true });
597
- }
598
- }
599
- function printDocument(template, doc, opts) {
600
- printHtml(buildPrintHtml(template, doc, opts));
601
- }
602
-
603
- // src/lib/agent-tools.ts
604
- var CATEGORY_ENUM = ["anamnesis", "evolution", "report", "contract", "general"];
605
- var FIELD_SHAPE = "A field object: { type, label, required?, placeholder?, options?[] }. type \u2208 title|text|memo|richtext|date|select|radio|tags|checkbox|image|gallery|budget (title = section heading, no input). options are plain strings, only for select/radio/tags.";
606
- var createOrUpdateFormTool = {
607
- id: "custom_forms.createOrUpdateForm",
608
- name: "createOrUpdateForm",
609
- description: "Creates or edits a form template (anamnese, contrato, ficha\u2026). Persisted server-side, so it works from ANY page \u2014 the user does NOT need the form builder open. Omit templateId to CREATE (name required); pass templateId to EDIT an existing form. Send the FULL desired field list in `fields` (replaces all fields), or `append_fields` to add to what already exists. Layout is automatic \u2014 never send positions. Understand the intent and propose a sensible field set in one call rather than asking field by field. The user confirms before it is saved.",
610
- icon: "FileText",
611
- mode: "persist",
612
- limitKey: "form_templates",
613
- permission: { feature: "custom_forms", action: "create" },
614
- execution: { plane: "server", kind: "rpc", rpc: "agent_forms_upsert_template" },
615
- category: "Forms",
616
- suggestions: [
617
- { label: "Monte um formul\xE1rio de anamnese" },
618
- { label: "Adicione um campo de alergias neste formul\xE1rio" }
619
- ],
620
- parameters: {
621
- type: "object",
622
- properties: {
623
- templateId: {
624
- type: "string",
625
- description: "Existing form id to EDIT (from context or listFormTemplates). Omit to create a new form."
626
- },
627
- name: { type: "string", description: "Form name \u2014 required when creating." },
628
- category: {
629
- type: "string",
630
- enum: [...CATEGORY_ENUM],
631
- description: "anamnesis | evolution | report | contract | general"
632
- },
633
- description: { type: "string", description: "Optional short description of the form." },
634
- fields: {
635
- type: "array",
636
- description: `The FULL desired field list \u2014 REPLACES every existing field. Each item: ${FIELD_SHAPE}`,
637
- items: { type: "object", description: FIELD_SHAPE }
638
- },
639
- append_fields: {
640
- type: "array",
641
- description: `Fields to ADD to the existing form, keeping current ones. Use this for incremental edits instead of \`fields\`. Each item: ${FIELD_SHAPE}`,
642
- items: { type: "object", description: FIELD_SHAPE }
643
- }
644
- }
645
- }
646
- };
647
- var FORM_BUILDER_INSTRUCTIONS = [
648
- "Voc\xEA est\xE1 na tela do CONSTRUTOR DE FORMUL\xC1RIOS. Para montar ou editar formul\xE1rios use a tool createOrUpdateForm (ela persiste no backend e funciona mesmo fora desta tela).",
649
- "O formul\xE1rio aberto agora \u2014 id, nome, categoria e campos com seus ids \u2014 est\xE1 no JSON da tela em contexto.",
650
- "Para EDITAR este formul\xE1rio, passe o templateId do contexto. Para adicionar campos mantendo os atuais, use append_fields; para redefinir tudo, use fields com a lista completa.",
651
- "Ao criar do zero: entenda a inten\xE7\xE3o e proponha um conjunto de campos enxuto numa \xFAnica chamada (n\xE3o pergunte campo a campo). Depois confirme em uma frase o que foi montado.",
652
- "Tipos de campo: title (cabe\xE7alho de se\xE7\xE3o, sem input), text, memo (multilinha), richtext, date, select, radio, tags, checkbox, image, gallery, budget. Layout \xE9 autom\xE1tico."
653
- ].join(" ");
654
-
655
- // src/lib/person-fields.ts
656
- var PERSON_FIELD_OPTIONS = [
657
- { value: "name", labelKey: "customForms.personField.name" },
658
- { value: "documentNumber", labelKey: "customForms.personField.documentNumber" },
659
- { value: "email", labelKey: "customForms.personField.email" },
660
- { value: "phone", labelKey: "customForms.personField.phone" },
661
- { value: "dateOfBirth", labelKey: "customForms.personField.dateOfBirth" },
662
- { value: "guardianName", labelKey: "customForms.personField.guardianName" },
663
- { value: "address", labelKey: "customForms.personField.address" },
664
- { value: "city", labelKey: "customForms.personField.city" },
665
- { value: "state", labelKey: "customForms.personField.state" }
666
- ];
667
- var toSnake = (k) => k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
668
- var toCamel = (k) => k.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
669
- function resolvePersonValue(person, mapKey) {
670
- if (!person || !mapKey) return void 0;
671
- for (const key of [mapKey, toSnake(mapKey), toCamel(mapKey)]) {
672
- const v = person[key];
673
- if (v != null && v !== "") return v;
674
- }
675
- return void 0;
676
- }
677
- function prefillFromPerson(schema, person, existing = {}) {
678
- const data = { ...existing };
679
- if (!schema) return data;
680
- for (const field of schema.fields) {
681
- const current = data[field.id];
682
- if (current != null && current !== "") continue;
683
- const mapped = field.map ? resolvePersonValue(person, field.map) : void 0;
684
- if (mapped != null) {
685
- data[field.id] = mapped;
686
- } else if (field.defaultValue != null) {
687
- data[field.id] = field.defaultValue;
688
- }
689
- }
690
- return data;
691
- }
692
-
693
- export { FORM_BUILDER_INSTRUCTIONS, FormRenderer, PERSON_FIELD_OPTIONS, buildPrintHtml, createOrUpdateFormTool, htmlToPlainText2 as htmlToPlainText, prefillFromPerson, printDocument };
694
- //# sourceMappingURL=chunk-2AIKIPGU.js.map
695
- //# sourceMappingURL=chunk-2AIKIPGU.js.map