@fayz-ai/plugin-forms 0.15.13 → 0.15.15
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/CustomFormsSettingsTab-EI4WKOYR.js +314 -0
- package/dist/CustomFormsSettingsTab-EI4WKOYR.js.map +1 -0
- package/dist/{FormBuilder-Q2Q42HAT.js → FormBuilder-7UXCXUOF.js} +7 -3
- package/dist/FormBuilder-7UXCXUOF.js.map +1 -0
- package/dist/PersonAnswerAlerts-MDBBY63K.js +98 -0
- package/dist/PersonAnswerAlerts-MDBBY63K.js.map +1 -0
- package/dist/PersonDocumentsWidget-RGN4UHS3.js +977 -0
- package/dist/PersonDocumentsWidget-RGN4UHS3.js.map +1 -0
- package/dist/chunk-5WW6FTNG.js +113 -0
- package/dist/chunk-5WW6FTNG.js.map +1 -0
- package/dist/chunk-66C3L4RE.js +17 -0
- package/dist/chunk-66C3L4RE.js.map +1 -0
- package/dist/chunk-BFXJLPKA.js +55 -0
- package/dist/chunk-BFXJLPKA.js.map +1 -0
- package/dist/chunk-RB7UYZLA.js +824 -0
- package/dist/chunk-RB7UYZLA.js.map +1 -0
- package/dist/chunk-X6CU44B4.js +223 -0
- package/dist/chunk-X6CU44B4.js.map +1 -0
- package/dist/chunk-YOFFKQD4.js +584 -0
- package/dist/chunk-YOFFKQD4.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -1507
- package/dist/index.js.map +1 -1
- package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/FormBuilder-Q2Q42HAT.js.map +0 -1
- package/dist/chunk-JRR7A5HN.js +0 -1678
- package/dist/chunk-JRR7A5HN.js.map +0 -1
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { useOptionalCustomFormsContext } from './chunk-66C3L4RE.js';
|
|
2
|
+
import React2 from 'react';
|
|
3
|
+
import { AlertTriangle, Camera, X, ScanFace, ImagePlus, Video, ShoppingBag, Plus, Trash2, Receipt, Package, Minus } from 'lucide-react';
|
|
4
|
+
import { Button, PickerList } from '@fayz-ai/ui';
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
import { describeError, formatCurrency } from '@fayz-ai/core';
|
|
7
|
+
|
|
8
|
+
function useMediaUrl(path) {
|
|
9
|
+
const ctx = useOptionalCustomFormsContext();
|
|
10
|
+
const [url, setUrl] = React2.useState(null);
|
|
11
|
+
React2.useEffect(() => {
|
|
12
|
+
if (!path) {
|
|
13
|
+
setUrl(null);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (/^(data:|blob:|https?:)/.test(path)) {
|
|
17
|
+
setUrl(path);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let alive = true;
|
|
21
|
+
Promise.resolve(ctx?.provider.mediaUrl?.(path) ?? null).then((u) => {
|
|
22
|
+
if (alive) setUrl(u);
|
|
23
|
+
}).catch(() => {
|
|
24
|
+
if (alive) setUrl(null);
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
alive = false;
|
|
28
|
+
};
|
|
29
|
+
}, [path, ctx]);
|
|
30
|
+
return url;
|
|
31
|
+
}
|
|
32
|
+
function useMediaUpload() {
|
|
33
|
+
const ctx = useOptionalCustomFormsContext();
|
|
34
|
+
const upload = ctx?.provider.uploadMedia;
|
|
35
|
+
return React2.useMemo(() => upload ? (blob, folder, name) => upload.call(ctx.provider, blob, folder, name) : null, [upload, ctx]);
|
|
36
|
+
}
|
|
37
|
+
async function compressImage(file, maxSide = 1600, quality = 0.85) {
|
|
38
|
+
if (!file.type.startsWith("image/") || file.type === "image/gif") return file;
|
|
39
|
+
try {
|
|
40
|
+
const bitmap = await createImageBitmap(file);
|
|
41
|
+
const scale = Math.min(1, maxSide / Math.max(bitmap.width, bitmap.height));
|
|
42
|
+
if (scale === 1 && file.size < 9e5) return file;
|
|
43
|
+
const canvas = document.createElement("canvas");
|
|
44
|
+
canvas.width = Math.round(bitmap.width * scale);
|
|
45
|
+
canvas.height = Math.round(bitmap.height * scale);
|
|
46
|
+
canvas.getContext("2d").drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
|
47
|
+
const out = await new Promise((r) => canvas.toBlob(r, "image/jpeg", quality));
|
|
48
|
+
return out ?? file;
|
|
49
|
+
} catch {
|
|
50
|
+
return file;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function isMedia(v) {
|
|
54
|
+
return !!v && typeof v === "object" && typeof v.path === "string";
|
|
55
|
+
}
|
|
56
|
+
function MediaImage({ value, alt, className, style }) {
|
|
57
|
+
const path = typeof value === "string" ? value : value?.path;
|
|
58
|
+
const url = useMediaUrl(path);
|
|
59
|
+
if (!url) return /* @__PURE__ */ jsx("div", { className: `animate-pulse bg-muted ${className ?? ""}`, style, "aria-busy": "true" });
|
|
60
|
+
return /* @__PURE__ */ jsx("img", { src: url, alt, className, style });
|
|
61
|
+
}
|
|
62
|
+
var brl = (n) => formatCurrency(n);
|
|
63
|
+
var stencilOf = (f) => f.config?.stencil ?? {};
|
|
64
|
+
var galleryOf = (f) => f.config?.gallery ?? {};
|
|
65
|
+
var alertOf = (f) => {
|
|
66
|
+
const a = f.config?.alert;
|
|
67
|
+
return a?.trigger && a.trigger !== "never" ? a : null;
|
|
68
|
+
};
|
|
69
|
+
var aspectRatioOf = (c) => c.aspect === "1:1" ? 1 : c.aspect === "4:3" ? 4 / 3 : 3 / 4;
|
|
70
|
+
function yesNoAnswer(v) {
|
|
71
|
+
const a = typeof v === "string" ? v : v?.answer;
|
|
72
|
+
return a === "sim" || a === "nao" ? a : "";
|
|
73
|
+
}
|
|
74
|
+
function answerAlerts(f, v) {
|
|
75
|
+
const a = alertOf(f);
|
|
76
|
+
return a && yesNoAnswer(v) === a.trigger ? a : null;
|
|
77
|
+
}
|
|
78
|
+
var ALERT_TONE = {
|
|
79
|
+
danger: "border-destructive/40 bg-destructive/5 text-destructive",
|
|
80
|
+
warning: "border-warning/40 bg-warning-soft text-warning-soft-foreground",
|
|
81
|
+
info: "border-info/40 bg-info-soft text-info-soft-foreground"
|
|
82
|
+
};
|
|
83
|
+
function AnswerAlertNote({ alert }) {
|
|
84
|
+
return /* @__PURE__ */ jsxs("p", { className: `mt-1.5 inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-[11px] font-medium ${ALERT_TONE[alert.severity ?? "danger"]}`, children: [
|
|
85
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-3 w-3 shrink-0" }),
|
|
86
|
+
alert.message || "Gera alerta na ficha do cliente"
|
|
87
|
+
] });
|
|
88
|
+
}
|
|
89
|
+
function YesNoButtons({ id, value, onChange, disabled }) {
|
|
90
|
+
return /* @__PURE__ */ jsx("div", { role: "radiogroup", className: "inline-flex rounded-lg border bg-muted/40 p-0.5", children: ["sim", "nao"].map((opt) => /* @__PURE__ */ jsx(
|
|
91
|
+
Button,
|
|
92
|
+
{
|
|
93
|
+
variant: "bare",
|
|
94
|
+
size: "bare",
|
|
95
|
+
id: `${id}-${opt}`,
|
|
96
|
+
type: "button",
|
|
97
|
+
role: "radio",
|
|
98
|
+
"aria-checked": value === opt,
|
|
99
|
+
disabled,
|
|
100
|
+
onClick: () => onChange(opt),
|
|
101
|
+
className: `min-w-14 rounded-md px-3 py-1 text-xs font-medium transition-colors ${value === opt ? "bg-primary text-primary-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
|
|
102
|
+
children: opt === "sim" ? "Sim" : "N\xE3o"
|
|
103
|
+
},
|
|
104
|
+
opt
|
|
105
|
+
)) });
|
|
106
|
+
}
|
|
107
|
+
function YesNoField({ field, value, onChange }) {
|
|
108
|
+
const alert = answerAlerts(field, value);
|
|
109
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
110
|
+
/* @__PURE__ */ jsx(YesNoButtons, { id: field.id, value: yesNoAnswer(value), onChange }),
|
|
111
|
+
alert && /* @__PURE__ */ jsx(AnswerAlertNote, { alert })
|
|
112
|
+
] });
|
|
113
|
+
}
|
|
114
|
+
function YesNoTextField({ field, value, onChange }) {
|
|
115
|
+
const v = value && typeof value === "object" ? value : { answer: yesNoAnswer(value) };
|
|
116
|
+
const showText = v.answer === "sim" || !!v.text?.trim();
|
|
117
|
+
const alert = answerAlerts(field, v);
|
|
118
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
119
|
+
/* @__PURE__ */ jsx(YesNoButtons, { id: field.id, value: v.answer, onChange: (a) => onChange({ ...v, answer: a }) }),
|
|
120
|
+
alert && /* @__PURE__ */ jsx(AnswerAlertNote, { alert }),
|
|
121
|
+
showText && /* @__PURE__ */ jsx(
|
|
122
|
+
"textarea",
|
|
123
|
+
{
|
|
124
|
+
value: v.text ?? "",
|
|
125
|
+
onChange: (e) => onChange({ ...v, text: e.target.value }),
|
|
126
|
+
placeholder: field.placeholder || "Descreva\u2026",
|
|
127
|
+
rows: 2,
|
|
128
|
+
className: "flex w-full rounded-input border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
] });
|
|
132
|
+
}
|
|
133
|
+
function StencilPhotoField({ field, value, onChange, onOpenCamera, uploading }) {
|
|
134
|
+
const cfg = stencilOf(field);
|
|
135
|
+
const ratio = aspectRatioOf(cfg);
|
|
136
|
+
const overlay = useMediaUrl(cfg.overlayPath);
|
|
137
|
+
if (uploading) {
|
|
138
|
+
return /* @__PURE__ */ jsx("div", { className: "w-full max-w-[320px] animate-pulse rounded-lg bg-muted", style: { aspectRatio: String(ratio) }, "aria-busy": "true" });
|
|
139
|
+
}
|
|
140
|
+
if (isMedia(value)) {
|
|
141
|
+
return /* @__PURE__ */ jsxs("div", { className: "max-w-[320px] space-y-2", children: [
|
|
142
|
+
/* @__PURE__ */ jsx(MediaImage, { value, alt: field.label, className: "w-full rounded-lg border object-cover", style: { aspectRatio: String(ratio) } }),
|
|
143
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
144
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", variant: "outline", className: "h-8 gap-1 text-xs", onClick: onOpenCamera, children: [
|
|
145
|
+
/* @__PURE__ */ jsx(Camera, { className: "h-3 w-3" }),
|
|
146
|
+
" Refazer"
|
|
147
|
+
] }),
|
|
148
|
+
/* @__PURE__ */ jsxs(
|
|
149
|
+
Button,
|
|
150
|
+
{
|
|
151
|
+
type: "button",
|
|
152
|
+
size: "sm",
|
|
153
|
+
variant: "outline",
|
|
154
|
+
className: "h-8 gap-1 text-xs text-destructive hover:text-destructive",
|
|
155
|
+
onClick: () => onChange(null),
|
|
156
|
+
children: [
|
|
157
|
+
/* @__PURE__ */ jsx(X, { className: "h-3 w-3" }),
|
|
158
|
+
" Excluir"
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
] }),
|
|
163
|
+
cfg.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: cfg.description })
|
|
164
|
+
] });
|
|
165
|
+
}
|
|
166
|
+
return /* @__PURE__ */ jsxs(
|
|
167
|
+
Button,
|
|
168
|
+
{
|
|
169
|
+
variant: "bare",
|
|
170
|
+
size: "bare",
|
|
171
|
+
type: "button",
|
|
172
|
+
onClick: onOpenCamera,
|
|
173
|
+
className: "font-normal whitespace-normal flex w-full max-w-[320px] flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed border-input bg-muted/20 p-3 text-muted-foreground transition-colors hover:border-primary hover:bg-muted/40 hover:text-primary",
|
|
174
|
+
style: { aspectRatio: String(ratio) },
|
|
175
|
+
children: [
|
|
176
|
+
overlay ? /* @__PURE__ */ jsx("img", { src: overlay, alt: "", className: "max-h-[70%] max-w-[70%] object-contain opacity-70" }) : /* @__PURE__ */ jsx(ScanFace, { className: "h-8 w-8" }),
|
|
177
|
+
/* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-center text-[11px] font-medium leading-tight", children: cfg.description || "Tocar para abrir c\xE2mera" })
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
function GalleryThumb({ item, onRemove }) {
|
|
183
|
+
const url = useMediaUrl(item.path);
|
|
184
|
+
return /* @__PURE__ */ jsxs("div", { className: "group relative aspect-square overflow-hidden rounded-lg border bg-muted", children: [
|
|
185
|
+
!url ? /* @__PURE__ */ jsx("div", { className: "h-full w-full animate-pulse bg-muted" }) : item.type === "video" ? /* @__PURE__ */ jsx("video", { src: url, className: "h-full w-full object-cover", controls: true, preload: "metadata" }) : /* @__PURE__ */ jsx("a", { href: url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx("img", { src: url, alt: item.name ?? "", className: "h-full w-full object-cover" }) }),
|
|
186
|
+
onRemove && /* @__PURE__ */ jsx(
|
|
187
|
+
Button,
|
|
188
|
+
{
|
|
189
|
+
variant: "bare",
|
|
190
|
+
size: "bare",
|
|
191
|
+
type: "button",
|
|
192
|
+
onClick: onRemove,
|
|
193
|
+
"aria-label": "Remover",
|
|
194
|
+
className: "font-normal absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-background/90 text-muted-foreground shadow hover:text-destructive",
|
|
195
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
] });
|
|
199
|
+
}
|
|
200
|
+
function GalleryView({ items }) {
|
|
201
|
+
return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-4", children: items.map((it) => /* @__PURE__ */ jsx(GalleryThumb, { item: it }, it.path)) });
|
|
202
|
+
}
|
|
203
|
+
function GalleryField({ field, value, onChange }) {
|
|
204
|
+
const cfg = galleryOf(field);
|
|
205
|
+
const allowPhoto = cfg.allowPhoto ?? true;
|
|
206
|
+
const allowVideo = cfg.allowVideo ?? true;
|
|
207
|
+
const upload = useMediaUpload();
|
|
208
|
+
const photoRef = React2.useRef(null);
|
|
209
|
+
const videoRef = React2.useRef(null);
|
|
210
|
+
const [pending, setPending] = React2.useState(0);
|
|
211
|
+
const [erro, setErro] = React2.useState(null);
|
|
212
|
+
const items = Array.isArray(value) ? value.filter(isMedia) : [];
|
|
213
|
+
const latest = React2.useRef(items);
|
|
214
|
+
latest.current = items;
|
|
215
|
+
async function add(files, kind) {
|
|
216
|
+
if (!files?.length || !upload) return;
|
|
217
|
+
setErro(null);
|
|
218
|
+
for (const file of Array.from(files)) {
|
|
219
|
+
setPending((n) => n + 1);
|
|
220
|
+
try {
|
|
221
|
+
const blob = kind === "photo" ? await compressImage(file) : file;
|
|
222
|
+
const { path } = await upload(blob, `uploads/${field.id}`, kind === "photo" ? "foto.jpg" : file.name);
|
|
223
|
+
latest.current = [...latest.current, { path, type: kind, name: file.name }];
|
|
224
|
+
onChange(latest.current);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
setErro(`N\xE3o foi poss\xEDvel enviar ${file.name}: ${describeError(e, "erro")}`);
|
|
227
|
+
} finally {
|
|
228
|
+
setPending((n) => n - 1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
233
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
234
|
+
allowPhoto && /* @__PURE__ */ jsxs(
|
|
235
|
+
Button,
|
|
236
|
+
{
|
|
237
|
+
type: "button",
|
|
238
|
+
variant: "outline",
|
|
239
|
+
size: "sm",
|
|
240
|
+
className: "h-8 gap-1.5 text-xs",
|
|
241
|
+
disabled: !upload,
|
|
242
|
+
onClick: () => photoRef.current?.click(),
|
|
243
|
+
children: [
|
|
244
|
+
/* @__PURE__ */ jsx(ImagePlus, { className: "h-3.5 w-3.5" }),
|
|
245
|
+
" Adicionar foto"
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
allowVideo && /* @__PURE__ */ jsxs(
|
|
250
|
+
Button,
|
|
251
|
+
{
|
|
252
|
+
type: "button",
|
|
253
|
+
variant: "outline",
|
|
254
|
+
size: "sm",
|
|
255
|
+
className: "h-8 gap-1.5 text-xs",
|
|
256
|
+
disabled: !upload,
|
|
257
|
+
onClick: () => videoRef.current?.click(),
|
|
258
|
+
children: [
|
|
259
|
+
/* @__PURE__ */ jsx(Video, { className: "h-3.5 w-3.5" }),
|
|
260
|
+
" Adicionar v\xEDdeo"
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
] }),
|
|
265
|
+
/* @__PURE__ */ jsx(
|
|
266
|
+
"input",
|
|
267
|
+
{
|
|
268
|
+
ref: photoRef,
|
|
269
|
+
type: "file",
|
|
270
|
+
accept: "image/*",
|
|
271
|
+
multiple: true,
|
|
272
|
+
className: "hidden",
|
|
273
|
+
onChange: (e) => {
|
|
274
|
+
add(e.target.files, "photo");
|
|
275
|
+
e.target.value = "";
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
/* @__PURE__ */ jsx(
|
|
280
|
+
"input",
|
|
281
|
+
{
|
|
282
|
+
ref: videoRef,
|
|
283
|
+
type: "file",
|
|
284
|
+
accept: "video/*",
|
|
285
|
+
multiple: true,
|
|
286
|
+
className: "hidden",
|
|
287
|
+
onChange: (e) => {
|
|
288
|
+
add(e.target.files, "video");
|
|
289
|
+
e.target.value = "";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
),
|
|
293
|
+
(items.length > 0 || pending > 0) && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-4", children: [
|
|
294
|
+
items.map((it) => /* @__PURE__ */ jsx(GalleryThumb, { item: it, onRemove: () => onChange(items.filter((x) => x.path !== it.path)) }, it.path)),
|
|
295
|
+
Array.from({ length: pending }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "aspect-square animate-pulse rounded-lg bg-muted", "aria-busy": "true" }, `p${i}`))
|
|
296
|
+
] }),
|
|
297
|
+
erro && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: erro })
|
|
298
|
+
] });
|
|
299
|
+
}
|
|
300
|
+
function Stepper({ value, onChange }) {
|
|
301
|
+
return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center rounded-lg border", children: [
|
|
302
|
+
/* @__PURE__ */ jsx(
|
|
303
|
+
Button,
|
|
304
|
+
{
|
|
305
|
+
variant: "bare",
|
|
306
|
+
size: "bare",
|
|
307
|
+
type: "button",
|
|
308
|
+
"aria-label": "Diminuir",
|
|
309
|
+
disabled: value <= 1,
|
|
310
|
+
onClick: () => onChange(value - 1),
|
|
311
|
+
className: "font-normal flex h-7 w-7 items-center justify-center text-muted-foreground hover:text-foreground disabled:opacity-30",
|
|
312
|
+
children: /* @__PURE__ */ jsx(Minus, { className: "h-3 w-3" })
|
|
313
|
+
}
|
|
314
|
+
),
|
|
315
|
+
/* @__PURE__ */ jsx("span", { className: "w-7 text-center text-sm tabular-nums", children: value }),
|
|
316
|
+
/* @__PURE__ */ jsx(
|
|
317
|
+
Button,
|
|
318
|
+
{
|
|
319
|
+
variant: "bare",
|
|
320
|
+
size: "bare",
|
|
321
|
+
type: "button",
|
|
322
|
+
"aria-label": "Aumentar",
|
|
323
|
+
onClick: () => onChange(value + 1),
|
|
324
|
+
className: "font-normal flex h-7 w-7 items-center justify-center text-muted-foreground hover:text-foreground",
|
|
325
|
+
children: /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" })
|
|
326
|
+
}
|
|
327
|
+
)
|
|
328
|
+
] });
|
|
329
|
+
}
|
|
330
|
+
function RecommendationField({ value, onChange, shortcuts = [] }) {
|
|
331
|
+
const ctx = useOptionalCustomFormsContext();
|
|
332
|
+
const items = Array.isArray(value) ? value : [];
|
|
333
|
+
const [picking, setPicking] = React2.useState(null);
|
|
334
|
+
const [catalog, setCatalog] = React2.useState(null);
|
|
335
|
+
const [erro, setErro] = React2.useState(null);
|
|
336
|
+
React2.useEffect(() => {
|
|
337
|
+
if (!picking || catalog) return;
|
|
338
|
+
let alive = true;
|
|
339
|
+
Promise.resolve(ctx?.provider.searchCatalog?.("") ?? []).then((r) => {
|
|
340
|
+
if (alive) setCatalog(r);
|
|
341
|
+
}).catch((e) => {
|
|
342
|
+
if (alive) {
|
|
343
|
+
setCatalog([]);
|
|
344
|
+
setErro(describeError(e, "N\xE3o foi poss\xEDvel ler o cat\xE1logo."));
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
return () => {
|
|
348
|
+
alive = false;
|
|
349
|
+
};
|
|
350
|
+
}, [picking, catalog, ctx]);
|
|
351
|
+
const has = (serviceId) => items.some((i) => i.serviceId === serviceId && !i.packageId);
|
|
352
|
+
const groups = React2.useMemo(() => {
|
|
353
|
+
const opts = (catalog ?? []).filter((c) => c.kind === picking);
|
|
354
|
+
if (picking === "package") {
|
|
355
|
+
return [{ id: "pk", options: opts.map((c) => ({ id: c.id, label: c.name, hint: brl(c.price) })) }];
|
|
356
|
+
}
|
|
357
|
+
const by = /* @__PURE__ */ new Map();
|
|
358
|
+
for (const c of opts) {
|
|
359
|
+
const g = c.group ?? "Outros";
|
|
360
|
+
if (!by.has(g)) by.set(g, []);
|
|
361
|
+
by.get(g).push(c);
|
|
362
|
+
}
|
|
363
|
+
return [...by.entries()].sort(([a], [b]) => a.localeCompare(b, "pt-BR")).map(([g, cs]) => ({
|
|
364
|
+
id: g,
|
|
365
|
+
label: `${g} (${cs.length})`,
|
|
366
|
+
options: cs.map((c) => ({ id: c.id, label: c.name, hint: brl(c.price), disabled: has(c.id) }))
|
|
367
|
+
}));
|
|
368
|
+
}, [catalog, picking, items]);
|
|
369
|
+
async function pick(id) {
|
|
370
|
+
const c = catalog?.find((x) => x.id === id);
|
|
371
|
+
if (!c) return;
|
|
372
|
+
if (c.kind === "service") {
|
|
373
|
+
if (!has(c.id)) onChange([...items, { serviceId: c.id, name: c.name, unitPrice: c.price, quantity: 1 }]);
|
|
374
|
+
setPicking(null);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const lines = await ctx?.provider.getPackageServices?.(c.id) ?? [];
|
|
379
|
+
if (!lines.length) {
|
|
380
|
+
setErro("Este pacote n\xE3o tem servi\xE7os cadastrados.");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
onChange([...items.filter((i) => i.packageId !== c.id), ...lines.map((l) => ({ ...l, packageId: c.id, packageName: c.name }))]);
|
|
384
|
+
setPicking(null);
|
|
385
|
+
} catch (e) {
|
|
386
|
+
setErro(describeError(e, "N\xE3o foi poss\xEDvel abrir o pacote."));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const freeShortcuts = shortcuts.filter((s) => !has(s.serviceId));
|
|
390
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
|
|
391
|
+
freeShortcuts.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-lg border border-warning/40 bg-warning-soft/50 p-2.5", children: [
|
|
392
|
+
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-xs font-semibold text-warning-soft-foreground", children: [
|
|
393
|
+
/* @__PURE__ */ jsx(ShoppingBag, { className: "h-3.5 w-3.5" }),
|
|
394
|
+
" Compras antecipadas deste cliente",
|
|
395
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-normal opacity-80", children: "\xB7 toque para indicar o que vai executar" })
|
|
396
|
+
] }),
|
|
397
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: freeShortcuts.map((s) => /* @__PURE__ */ jsxs(
|
|
398
|
+
Button,
|
|
399
|
+
{
|
|
400
|
+
variant: "bare",
|
|
401
|
+
size: "bare",
|
|
402
|
+
type: "button",
|
|
403
|
+
onClick: () => onChange([...items, { serviceId: s.serviceId, name: s.name, unitPrice: s.unitPrice, quantity: 1 }]),
|
|
404
|
+
className: "font-normal inline-flex items-center gap-1.5 rounded-full border bg-background px-2.5 py-1 text-xs hover:bg-muted",
|
|
405
|
+
children: [
|
|
406
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
|
|
407
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: s.name }),
|
|
408
|
+
s.hint && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-success", children: s.hint })
|
|
409
|
+
]
|
|
410
|
+
},
|
|
411
|
+
s.serviceId
|
|
412
|
+
)) })
|
|
413
|
+
] }),
|
|
414
|
+
items.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-lg border", children: [
|
|
415
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[1fr_auto_auto] gap-2 border-b bg-muted/50 px-3 py-2 text-xs font-semibold text-muted-foreground", children: [
|
|
416
|
+
/* @__PURE__ */ jsx("span", { children: "Procedimento indicado" }),
|
|
417
|
+
/* @__PURE__ */ jsx("span", { className: "w-[84px] text-center", children: "Qtd." }),
|
|
418
|
+
/* @__PURE__ */ jsx("span", { className: "w-6" })
|
|
419
|
+
] }),
|
|
420
|
+
items.map((it, idx) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[1fr_auto_auto] items-center gap-2 border-b px-3 py-2 last:border-0", children: [
|
|
421
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
422
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: it.name }),
|
|
423
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground", children: [
|
|
424
|
+
brl(it.unitPrice * it.quantity),
|
|
425
|
+
it.packageName ? ` \xB7 pacote ${it.packageName}` : ""
|
|
426
|
+
] })
|
|
427
|
+
] }),
|
|
428
|
+
/* @__PURE__ */ jsx(Stepper, { value: it.quantity, onChange: (q) => onChange(items.map((x, i) => i === idx ? { ...x, quantity: q } : x)) }),
|
|
429
|
+
/* @__PURE__ */ jsx(
|
|
430
|
+
Button,
|
|
431
|
+
{
|
|
432
|
+
variant: "bare",
|
|
433
|
+
size: "bare",
|
|
434
|
+
type: "button",
|
|
435
|
+
"aria-label": `Remover ${it.name}`,
|
|
436
|
+
onClick: () => onChange(items.filter((_, i) => i !== idx)),
|
|
437
|
+
className: "font-normal flex h-6 w-6 items-center justify-center text-muted-foreground hover:text-destructive",
|
|
438
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
] }, `${it.packageId ?? ""}:${it.serviceId}`))
|
|
442
|
+
] }) : !picking && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg border-2 border-dashed border-input p-3 text-sm text-muted-foreground", children: [
|
|
443
|
+
/* @__PURE__ */ jsx(Receipt, { className: "h-5 w-5 shrink-0" }),
|
|
444
|
+
'Indique procedimentos para o cliente \u2014 eles v\xE3o para o CRM e para "O que o cliente deseja contratar?".'
|
|
445
|
+
] }),
|
|
446
|
+
picking ? /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-lg border", children: [
|
|
447
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-3 py-2", children: [
|
|
448
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold", children: picking === "package" ? "Indicar pacote" : "Adicionar servi\xE7os" }),
|
|
449
|
+
/* @__PURE__ */ jsx(
|
|
450
|
+
Button,
|
|
451
|
+
{
|
|
452
|
+
variant: "bare",
|
|
453
|
+
size: "bare",
|
|
454
|
+
type: "button",
|
|
455
|
+
onClick: () => setPicking(null),
|
|
456
|
+
"aria-label": "Fechar lista",
|
|
457
|
+
className: "font-normal text-muted-foreground hover:text-foreground",
|
|
458
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
] }),
|
|
462
|
+
catalog === null ? /* @__PURE__ */ jsx("div", { className: "space-y-1.5 p-3", "aria-busy": "true", children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ jsx("div", { className: "h-8 animate-pulse rounded-md bg-muted" }, i)) }) : /* @__PURE__ */ jsx(
|
|
463
|
+
PickerList,
|
|
464
|
+
{
|
|
465
|
+
groups,
|
|
466
|
+
onSelect: pick,
|
|
467
|
+
searchThreshold: 0,
|
|
468
|
+
listClassName: "max-h-72",
|
|
469
|
+
searchPlaceholder: picking === "package" ? "Buscar pacote\u2026" : "Buscar servi\xE7o\u2026",
|
|
470
|
+
emptyLabel: picking === "package" ? "Nenhum pacote ativo" : "Nenhum servi\xE7o encontrado"
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [
|
|
474
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", className: "justify-center gap-2", onClick: () => setPicking("service"), children: [
|
|
475
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
476
|
+
" Adicionar indica\xE7\xE3o"
|
|
477
|
+
] }),
|
|
478
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", className: "justify-center gap-2", onClick: () => setPicking("package"), children: [
|
|
479
|
+
/* @__PURE__ */ jsx(Package, { className: "h-4 w-4" }),
|
|
480
|
+
" Indicar pacote"
|
|
481
|
+
] })
|
|
482
|
+
] }),
|
|
483
|
+
erro && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: erro })
|
|
484
|
+
] });
|
|
485
|
+
}
|
|
486
|
+
function RecommendationView({ items }) {
|
|
487
|
+
if (!items.length) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/50", children: "\u2014" });
|
|
488
|
+
return /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: items.map((it) => /* @__PURE__ */ jsxs("li", { className: "flex items-baseline justify-between gap-3 text-sm", children: [
|
|
489
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
490
|
+
it.quantity > 1 ? `${it.quantity}\xD7 ` : "",
|
|
491
|
+
it.name
|
|
492
|
+
] }),
|
|
493
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: brl(it.unitPrice * it.quantity) })
|
|
494
|
+
] }, `${it.packageId ?? ""}:${it.serviceId}`)) });
|
|
495
|
+
}
|
|
496
|
+
function SignatureField({ field, value, onChange }) {
|
|
497
|
+
const canvasRef = React2.useRef(null);
|
|
498
|
+
const drawing = React2.useRef(false);
|
|
499
|
+
const dirty = React2.useRef(false);
|
|
500
|
+
const upload = useMediaUpload();
|
|
501
|
+
const [saving, setSaving] = React2.useState(false);
|
|
502
|
+
const [erro, setErro] = React2.useState(null);
|
|
503
|
+
React2.useEffect(() => {
|
|
504
|
+
const c = canvasRef.current;
|
|
505
|
+
if (!c) return;
|
|
506
|
+
const ratio = window.devicePixelRatio || 1;
|
|
507
|
+
c.width = c.offsetWidth * ratio;
|
|
508
|
+
c.height = c.offsetHeight * ratio;
|
|
509
|
+
const ctx = c.getContext("2d");
|
|
510
|
+
ctx.scale(ratio, ratio);
|
|
511
|
+
ctx.lineWidth = 2;
|
|
512
|
+
ctx.lineCap = "round";
|
|
513
|
+
ctx.strokeStyle = "#111827";
|
|
514
|
+
}, [isMedia(value)]);
|
|
515
|
+
if (isMedia(value)) {
|
|
516
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
517
|
+
/* @__PURE__ */ jsx(MediaImage, { value, alt: "Assinatura", className: "max-h-32 rounded-md border bg-white" }),
|
|
518
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onChange(null), children: "Assinar novamente" })
|
|
519
|
+
] });
|
|
520
|
+
}
|
|
521
|
+
const pos = (e) => {
|
|
522
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
523
|
+
return [e.clientX - r.left, e.clientY - r.top];
|
|
524
|
+
};
|
|
525
|
+
async function confirm() {
|
|
526
|
+
const c = canvasRef.current;
|
|
527
|
+
if (!c || !dirty.current || !upload) return;
|
|
528
|
+
setSaving(true);
|
|
529
|
+
setErro(null);
|
|
530
|
+
try {
|
|
531
|
+
const blob = await new Promise((r) => c.toBlob(r, "image/png"));
|
|
532
|
+
if (!blob) return;
|
|
533
|
+
const { path } = await upload(blob, `uploads/${field.id}`, "assinatura.png");
|
|
534
|
+
onChange({ path, type: "signature" });
|
|
535
|
+
} catch (e) {
|
|
536
|
+
setErro(describeError(e, "N\xE3o foi poss\xEDvel guardar a assinatura."));
|
|
537
|
+
} finally {
|
|
538
|
+
setSaving(false);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function clear() {
|
|
542
|
+
const c = canvasRef.current;
|
|
543
|
+
if (!c) return;
|
|
544
|
+
c.getContext("2d").clearRect(0, 0, c.width, c.height);
|
|
545
|
+
dirty.current = false;
|
|
546
|
+
}
|
|
547
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
548
|
+
/* @__PURE__ */ jsx(
|
|
549
|
+
"canvas",
|
|
550
|
+
{
|
|
551
|
+
ref: canvasRef,
|
|
552
|
+
className: "h-36 w-full touch-none rounded-md border bg-white",
|
|
553
|
+
onPointerDown: (e) => {
|
|
554
|
+
drawing.current = true;
|
|
555
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
556
|
+
const ctx = e.currentTarget.getContext("2d");
|
|
557
|
+
const [x, y] = pos(e);
|
|
558
|
+
ctx.beginPath();
|
|
559
|
+
ctx.moveTo(x, y);
|
|
560
|
+
},
|
|
561
|
+
onPointerMove: (e) => {
|
|
562
|
+
if (!drawing.current) return;
|
|
563
|
+
const ctx = e.currentTarget.getContext("2d");
|
|
564
|
+
const [x, y] = pos(e);
|
|
565
|
+
ctx.lineTo(x, y);
|
|
566
|
+
ctx.stroke();
|
|
567
|
+
dirty.current = true;
|
|
568
|
+
},
|
|
569
|
+
onPointerUp: () => {
|
|
570
|
+
drawing.current = false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
),
|
|
574
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
575
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: clear, children: "Limpar" }),
|
|
576
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: confirm, disabled: saving || !upload, children: saving ? "Guardando\u2026" : "Confirmar assinatura" })
|
|
577
|
+
] }),
|
|
578
|
+
erro && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: erro })
|
|
579
|
+
] });
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export { AnswerAlertNote, GalleryField, GalleryView, MediaImage, RecommendationField, RecommendationView, SignatureField, StencilPhotoField, YesNoField, YesNoTextField, alertOf, answerAlerts, aspectRatioOf, isMedia, stencilOf, useMediaUpload, useMediaUrl, yesNoAnswer };
|
|
583
|
+
//# sourceMappingURL=chunk-YOFFKQD4.js.map
|
|
584
|
+
//# sourceMappingURL=chunk-YOFFKQD4.js.map
|