@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,977 @@
|
|
|
1
|
+
import { getAllDocumentTypes, CATEGORY_ORDER, categoryLabel, documentToTimelineItem } from './chunk-5WW6FTNG.js';
|
|
2
|
+
import { prefillFromPerson, printDocument } from './chunk-X6CU44B4.js';
|
|
3
|
+
import { FormRenderer, htmlToPlainText } from './chunk-RB7UYZLA.js';
|
|
4
|
+
import { isMedia, yesNoAnswer } from './chunk-YOFFKQD4.js';
|
|
5
|
+
import './chunk-66C3L4RE.js';
|
|
6
|
+
import React3, { useCallback, useState, useRef, useEffect, useMemo } from 'react';
|
|
7
|
+
import { ArrowLeft, Image, Paperclip, Printer, Pencil, Plus, ChevronDown, Search, EyeOff, Eye, Upload, ClipboardList, Archive } from 'lucide-react';
|
|
8
|
+
import { Button, Badge, useSaveBar, Dropdown, DropdownTrigger, DropdownContent, Input, DropdownSeparator, DropdownLabel, ConfirmDialog } from '@fayz-ai/ui';
|
|
9
|
+
import { useTranslation } from '@fayz-ai/core';
|
|
10
|
+
import { useLimitGuard, invalidateLimit, invalidateTabCounts, TimelineList } from '@fayz-ai/admin';
|
|
11
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
12
|
+
|
|
13
|
+
var MAX_FIELDS = 8;
|
|
14
|
+
var MAX_CHARS = 280;
|
|
15
|
+
function isEmpty(value) {
|
|
16
|
+
if (value == null) return true;
|
|
17
|
+
if (typeof value === "string") return value.trim() === "";
|
|
18
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
function asText(value) {
|
|
22
|
+
if (Array.isArray(value)) return value.map((v) => String(v)).join(", ");
|
|
23
|
+
if (typeof value === "boolean") return value ? "Sim" : "N\xE3o";
|
|
24
|
+
return htmlToPlainText(String(value)) ?? String(value);
|
|
25
|
+
}
|
|
26
|
+
function PreviewMedia({ items, provider }) {
|
|
27
|
+
const [urls, setUrls] = useState([]);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let alive = true;
|
|
30
|
+
Promise.all(items.map((m) => Promise.resolve(provider.mediaUrl?.(m.path) ?? null).catch(() => null))).then((u) => {
|
|
31
|
+
if (alive) setUrls(u);
|
|
32
|
+
});
|
|
33
|
+
return () => {
|
|
34
|
+
alive = false;
|
|
35
|
+
};
|
|
36
|
+
}, [items.map((m) => m.path).join("|"), provider]);
|
|
37
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 pt-1", children: items.map((m, i) => urls[i] ? m.type === "video" ? /* @__PURE__ */ jsx("video", { src: urls[i], className: "h-20 w-20 rounded-md border object-cover", preload: "metadata" }, m.path) : /* @__PURE__ */ jsx("img", { src: urls[i], alt: "", className: `h-20 rounded-md border object-cover ${m.type === "signature" ? "bg-white" : "w-16"}` }, m.path) : /* @__PURE__ */ jsx("div", { className: "h-20 w-16 animate-pulse rounded-md bg-muted" }, m.path)) });
|
|
38
|
+
}
|
|
39
|
+
function mediaOf(field, value) {
|
|
40
|
+
if (field.type === "stencil_photo" || field.type === "signature") return isMedia(value) ? [value] : null;
|
|
41
|
+
if (field.type === "gallery") return Array.isArray(value) ? value.filter(isMedia) : null;
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function fieldText(field, value) {
|
|
45
|
+
if (field.type === "yes_no" || field.type === "yes_no_text") {
|
|
46
|
+
const a = yesNoAnswer(value);
|
|
47
|
+
const extra = value && typeof value === "object" ? value.text?.trim() : "";
|
|
48
|
+
return `${a === "sim" ? "Sim" : a === "nao" ? "N\xE3o" : ""}${extra ? ` \u2014 ${extra}` : ""}`;
|
|
49
|
+
}
|
|
50
|
+
if (field.type === "recommendation" && Array.isArray(value)) {
|
|
51
|
+
return value.map((l) => `${l.quantity > 1 ? `${l.quantity}\xD7 ` : ""}${l.name}`).join(" \xB7 ");
|
|
52
|
+
}
|
|
53
|
+
return asText(value);
|
|
54
|
+
}
|
|
55
|
+
function truncate(text) {
|
|
56
|
+
return text.length > MAX_CHARS ? `${text.slice(0, MAX_CHARS).trimEnd()}\u2026` : text;
|
|
57
|
+
}
|
|
58
|
+
function DocumentPreview({
|
|
59
|
+
document: doc,
|
|
60
|
+
provider,
|
|
61
|
+
onOpen
|
|
62
|
+
}) {
|
|
63
|
+
const t = useTranslation();
|
|
64
|
+
const [template, setTemplate] = useState(null);
|
|
65
|
+
const [loading, setLoading] = useState(true);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
let alive = true;
|
|
68
|
+
if (!doc.templateId) {
|
|
69
|
+
setLoading(false);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
void provider.getTemplateById(doc.templateId).then((found) => {
|
|
73
|
+
if (alive) {
|
|
74
|
+
setTemplate(found);
|
|
75
|
+
setLoading(false);
|
|
76
|
+
}
|
|
77
|
+
}).catch(() => {
|
|
78
|
+
if (alive) setLoading(false);
|
|
79
|
+
});
|
|
80
|
+
return () => {
|
|
81
|
+
alive = false;
|
|
82
|
+
};
|
|
83
|
+
}, [doc.templateId, provider]);
|
|
84
|
+
if (doc.kind === "image" && doc.fileUrl) {
|
|
85
|
+
return /* @__PURE__ */ jsx(
|
|
86
|
+
"img",
|
|
87
|
+
{
|
|
88
|
+
src: doc.fileUrl,
|
|
89
|
+
alt: doc.fileName ?? "",
|
|
90
|
+
className: "max-h-64 rounded-lg object-contain"
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (doc.kind === "attachment") {
|
|
95
|
+
return /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
96
|
+
doc.fileName ?? t("customForms.documentFallbackTitle"),
|
|
97
|
+
doc.fileSize ? ` \xB7 ${(doc.fileSize / 1024).toFixed(1)} KB` : ""
|
|
98
|
+
] });
|
|
99
|
+
}
|
|
100
|
+
if (loading) {
|
|
101
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
102
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-1/3 animate-pulse rounded bg-muted" }),
|
|
103
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-2/3 animate-pulse rounded bg-muted" })
|
|
104
|
+
] });
|
|
105
|
+
}
|
|
106
|
+
const fields = template?.schema?.fields ?? [];
|
|
107
|
+
const answered = fields.filter((f) => f.type !== "title" && !isEmpty(doc.data?.[f.id])).sort((a, b) => a.row - b.row || a.col - b.col);
|
|
108
|
+
if (answered.length === 0) {
|
|
109
|
+
return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("customForms.previewEmpty") });
|
|
110
|
+
}
|
|
111
|
+
const shown = answered.slice(0, MAX_FIELDS);
|
|
112
|
+
const hidden = answered.length - shown.length;
|
|
113
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
114
|
+
/* @__PURE__ */ jsx("dl", { className: "grid gap-x-6 gap-y-2 sm:grid-cols-2", children: shown.map((field) => {
|
|
115
|
+
const media = mediaOf(field, doc.data[field.id]);
|
|
116
|
+
const text = media ? "" : truncate(fieldText(field, doc.data[field.id]));
|
|
117
|
+
const wide = field.type === "memo" || field.type === "richtext" || field.type === "gallery" || text.length > 60;
|
|
118
|
+
return /* @__PURE__ */ jsxs("div", { className: `min-w-0 ${wide ? "sm:col-span-2" : ""}`, children: [
|
|
119
|
+
/* @__PURE__ */ jsx("dt", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: htmlToPlainText(field.label) ?? field.label }),
|
|
120
|
+
/* @__PURE__ */ jsx("dd", { className: "whitespace-pre-wrap text-sm leading-6 text-foreground", children: media ? /* @__PURE__ */ jsx(PreviewMedia, { items: media, provider }) : text })
|
|
121
|
+
] }, field.id);
|
|
122
|
+
}) }),
|
|
123
|
+
hidden > 0 && /* @__PURE__ */ jsx(
|
|
124
|
+
Button,
|
|
125
|
+
{
|
|
126
|
+
variant: "bare",
|
|
127
|
+
size: "bare",
|
|
128
|
+
type: "button",
|
|
129
|
+
onClick: onOpen,
|
|
130
|
+
className: "text-xs font-medium text-primary hover:underline",
|
|
131
|
+
children: t("customForms.previewMore", { count: String(hidden) })
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
] });
|
|
135
|
+
}
|
|
136
|
+
var ACTIVE_STATUSES = ["draft", "completed", "signed"];
|
|
137
|
+
var EXPAND_KEY = "fayz.forms.documents.expandAll";
|
|
138
|
+
function DocumentList({ personId, provider, store, onView, onFileDrop, kinds }) {
|
|
139
|
+
const acceptsFiles = Boolean(onFileDrop) && (!kinds || kinds.includes("attachment"));
|
|
140
|
+
const t = useTranslation();
|
|
141
|
+
const [documents, setDocuments] = useState([]);
|
|
142
|
+
const [total, setTotal] = useState(0);
|
|
143
|
+
const [page, setPage] = useState(1);
|
|
144
|
+
const [loading, setLoading] = useState(true);
|
|
145
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
146
|
+
const [counts, setCounts] = useState({});
|
|
147
|
+
const [bucket, setBucket] = useState(void 0);
|
|
148
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
149
|
+
const [expandAll, setExpandAll] = useState(() => {
|
|
150
|
+
try {
|
|
151
|
+
return localStorage.getItem(EXPAND_KEY) === "1";
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
const toggleExpandAll = useCallback(() => {
|
|
157
|
+
setExpandAll((prev) => {
|
|
158
|
+
try {
|
|
159
|
+
localStorage.setItem(EXPAND_KEY, prev ? "0" : "1");
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
return !prev;
|
|
163
|
+
});
|
|
164
|
+
}, []);
|
|
165
|
+
const [openIds, setOpenIds] = useState(/* @__PURE__ */ new Set());
|
|
166
|
+
const dragCounter = useRef(0);
|
|
167
|
+
const PAGE_SIZE = 20;
|
|
168
|
+
const queryFor = useCallback((nextPage) => ({
|
|
169
|
+
personId,
|
|
170
|
+
kinds,
|
|
171
|
+
templateCategory: bucket && bucket !== "file" ? bucket : void 0,
|
|
172
|
+
...bucket === "file" ? { kinds: ["image", "attachment"] } : {},
|
|
173
|
+
status: ACTIVE_STATUSES,
|
|
174
|
+
page: nextPage,
|
|
175
|
+
pageSize: PAGE_SIZE
|
|
176
|
+
}), [personId, kinds, bucket]);
|
|
177
|
+
const fetchDocs = useCallback(async () => {
|
|
178
|
+
setLoading(true);
|
|
179
|
+
try {
|
|
180
|
+
const result = await provider.getDocuments(queryFor(1));
|
|
181
|
+
setDocuments(result.data);
|
|
182
|
+
setTotal(result.total);
|
|
183
|
+
setPage(1);
|
|
184
|
+
} finally {
|
|
185
|
+
setLoading(false);
|
|
186
|
+
}
|
|
187
|
+
}, [provider, queryFor]);
|
|
188
|
+
const fetchCounts = useCallback(async () => {
|
|
189
|
+
try {
|
|
190
|
+
setCounts(await provider.getDocumentCounts({ personId, kinds }));
|
|
191
|
+
} catch {
|
|
192
|
+
setCounts({});
|
|
193
|
+
}
|
|
194
|
+
}, [provider, personId, kinds]);
|
|
195
|
+
const loadMore = useCallback(async () => {
|
|
196
|
+
setLoadingMore(true);
|
|
197
|
+
try {
|
|
198
|
+
const next = page + 1;
|
|
199
|
+
const result = await provider.getDocuments(queryFor(next));
|
|
200
|
+
setDocuments((prev) => {
|
|
201
|
+
const seen = new Set(prev.map((d) => d.id));
|
|
202
|
+
return [...prev, ...result.data.filter((d) => !seen.has(d.id))];
|
|
203
|
+
});
|
|
204
|
+
setTotal(result.total);
|
|
205
|
+
setPage(next);
|
|
206
|
+
} finally {
|
|
207
|
+
setLoadingMore(false);
|
|
208
|
+
}
|
|
209
|
+
}, [provider, queryFor, page]);
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
const timer = setTimeout(() => fetchDocs(), 50);
|
|
212
|
+
return () => clearTimeout(timer);
|
|
213
|
+
}, [fetchDocs]);
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
void fetchCounts();
|
|
216
|
+
}, [fetchCounts]);
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
const unsub = store.subscribe(() => {
|
|
219
|
+
void fetchDocs();
|
|
220
|
+
void fetchCounts();
|
|
221
|
+
});
|
|
222
|
+
return unsub;
|
|
223
|
+
}, [store, fetchDocs, fetchCounts]);
|
|
224
|
+
const [archiving, setArchiving] = useState(null);
|
|
225
|
+
const handleArchive = useCallback(async () => {
|
|
226
|
+
const doc = archiving;
|
|
227
|
+
setArchiving(null);
|
|
228
|
+
if (!doc) return;
|
|
229
|
+
await provider.updateDocument(doc.id, { status: "archived" });
|
|
230
|
+
setDocuments((prev) => prev.filter((d) => d.id !== doc.id));
|
|
231
|
+
setTotal((n) => Math.max(0, n - 1));
|
|
232
|
+
void fetchCounts();
|
|
233
|
+
invalidateTabCounts();
|
|
234
|
+
}, [provider, archiving, fetchCounts]);
|
|
235
|
+
const handleDragEnter = useCallback((e) => {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
dragCounter.current++;
|
|
238
|
+
setIsDragOver(true);
|
|
239
|
+
}, []);
|
|
240
|
+
const handleDragLeave = useCallback((e) => {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
dragCounter.current--;
|
|
243
|
+
if (dragCounter.current === 0) setIsDragOver(false);
|
|
244
|
+
}, []);
|
|
245
|
+
const handleDragOver = useCallback((e) => {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
}, []);
|
|
248
|
+
const handleDrop = useCallback((e) => {
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
dragCounter.current = 0;
|
|
251
|
+
setIsDragOver(false);
|
|
252
|
+
const files = Array.from(e.dataTransfer.files);
|
|
253
|
+
if (files.length > 0 && onFileDrop) {
|
|
254
|
+
onFileDrop(files);
|
|
255
|
+
}
|
|
256
|
+
}, [onFileDrop]);
|
|
257
|
+
const buckets = useMemo(() => {
|
|
258
|
+
const order = [...CATEGORY_ORDER, "file"];
|
|
259
|
+
return order.filter((b) => (counts[b] ?? 0) > 0).map((b) => ({
|
|
260
|
+
id: b,
|
|
261
|
+
label: b === "file" ? t("customForms.bucket.file") : categoryLabel(b),
|
|
262
|
+
count: counts[b] ?? 0
|
|
263
|
+
}));
|
|
264
|
+
}, [counts, t]);
|
|
265
|
+
const totalAll = useMemo(
|
|
266
|
+
() => Object.values(counts).reduce((sum, n) => sum + n, 0),
|
|
267
|
+
[counts]
|
|
268
|
+
);
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
if (bucket && !buckets.some((b) => b.id === bucket)) setBucket(void 0);
|
|
271
|
+
}, [buckets, bucket]);
|
|
272
|
+
const visible = documents;
|
|
273
|
+
const items = useMemo(
|
|
274
|
+
() => visible.map((doc) => documentToTimelineItem(doc, t("customForms.documentFallbackTitle"))),
|
|
275
|
+
[visible, t]
|
|
276
|
+
);
|
|
277
|
+
const byId = useMemo(() => new Map(visible.map((d) => [d.id, d])), [visible]);
|
|
278
|
+
const expandedIds = useMemo(() => {
|
|
279
|
+
if (!expandAll) return openIds;
|
|
280
|
+
const open = new Set(visible.map((d) => d.id));
|
|
281
|
+
for (const id of openIds) open.delete(id);
|
|
282
|
+
return open;
|
|
283
|
+
}, [expandAll, openIds, visible]);
|
|
284
|
+
const toggleOne = useCallback((id) => {
|
|
285
|
+
setOpenIds((prev) => {
|
|
286
|
+
const next = new Set(prev);
|
|
287
|
+
if (next.has(id)) next.delete(id);
|
|
288
|
+
else next.add(id);
|
|
289
|
+
return next;
|
|
290
|
+
});
|
|
291
|
+
}, []);
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
setOpenIds(/* @__PURE__ */ new Set());
|
|
294
|
+
}, [expandAll]);
|
|
295
|
+
if (loading) {
|
|
296
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-4 py-4", children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
297
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 w-9 rounded-lg bg-muted animate-pulse shrink-0" }),
|
|
298
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
|
|
299
|
+
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-2/3 bg-muted animate-pulse rounded" }),
|
|
300
|
+
/* @__PURE__ */ jsx("div", { className: "h-2.5 w-1/3 bg-muted animate-pulse rounded" })
|
|
301
|
+
] })
|
|
302
|
+
] }, i)) });
|
|
303
|
+
}
|
|
304
|
+
const filters = (buckets.length > 1 || totalAll > 0) && /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-1.5", children: [
|
|
305
|
+
buckets.length > 1 && [{ id: void 0, label: t("customForms.bucket.all"), count: totalAll }, ...buckets].map((chip) => {
|
|
306
|
+
const active = chip.id === bucket;
|
|
307
|
+
return /* @__PURE__ */ jsxs(
|
|
308
|
+
Button,
|
|
309
|
+
{
|
|
310
|
+
variant: "bare",
|
|
311
|
+
size: "bare",
|
|
312
|
+
type: "button",
|
|
313
|
+
onClick: () => setBucket(chip.id),
|
|
314
|
+
className: `gap-0 font-normal rounded-full border px-2.5 py-1 text-xs transition-colors ${active ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
315
|
+
children: [
|
|
316
|
+
chip.label,
|
|
317
|
+
/* @__PURE__ */ jsx("span", { className: "ml-1.5 tabular-nums opacity-60", children: chip.count })
|
|
318
|
+
]
|
|
319
|
+
},
|
|
320
|
+
chip.id ?? "__all__"
|
|
321
|
+
);
|
|
322
|
+
}),
|
|
323
|
+
/* @__PURE__ */ jsxs(
|
|
324
|
+
Button,
|
|
325
|
+
{
|
|
326
|
+
variant: "bare",
|
|
327
|
+
size: "bare",
|
|
328
|
+
type: "button",
|
|
329
|
+
onClick: toggleExpandAll,
|
|
330
|
+
"aria-pressed": expandAll,
|
|
331
|
+
title: t(expandAll ? "customForms.hideContent" : "customForms.showContent"),
|
|
332
|
+
className: `font-normal ml-auto flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors ${expandAll ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
333
|
+
children: [
|
|
334
|
+
expandAll ? /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" }),
|
|
335
|
+
t(expandAll ? "customForms.hideContent" : "customForms.showContent")
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
] });
|
|
340
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
341
|
+
filters,
|
|
342
|
+
/* @__PURE__ */ jsxs(
|
|
343
|
+
"div",
|
|
344
|
+
{
|
|
345
|
+
className: `relative min-h-[200px] rounded-xl border-2 transition-colors ${isDragOver ? "border-dashed border-primary bg-primary/5" : documents.length === 0 && acceptsFiles ? "border-dashed border-border" : "border-transparent"}`,
|
|
346
|
+
...acceptsFiles ? {
|
|
347
|
+
onDragEnter: handleDragEnter,
|
|
348
|
+
onDragLeave: handleDragLeave,
|
|
349
|
+
onDragOver: handleDragOver,
|
|
350
|
+
onDrop: handleDrop
|
|
351
|
+
} : {},
|
|
352
|
+
children: [
|
|
353
|
+
isDragOver && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-10 flex flex-col items-center justify-center rounded-xl bg-primary/5", children: [
|
|
354
|
+
/* @__PURE__ */ jsx(Upload, { className: "h-8 w-8 text-primary/50 mb-2" }),
|
|
355
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-primary/70", children: t("customForms.dropFilesHere") })
|
|
356
|
+
] }),
|
|
357
|
+
documents.length > 0 && visible.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("customForms.noDocumentsInFilter") }) }),
|
|
358
|
+
documents.length === 0 && !isDragOver && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [
|
|
359
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted mb-3", children: acceptsFiles ? /* @__PURE__ */ jsx(Paperclip, { className: "h-5 w-5 text-muted-foreground" }) : /* @__PURE__ */ jsx(ClipboardList, { className: "h-5 w-5 text-muted-foreground" }) }),
|
|
360
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t(acceptsFiles ? "customForms.noFiles" : "customForms.noRecords") }),
|
|
361
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 max-w-xs", children: t(acceptsFiles ? "customForms.noFilesDescription" : "customForms.noRecordsDescription") }),
|
|
362
|
+
acceptsFiles && /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground mt-3 flex items-center gap-1", children: [
|
|
363
|
+
/* @__PURE__ */ jsx(Upload, { className: "h-3 w-3" }),
|
|
364
|
+
t("customForms.dragFilesHere")
|
|
365
|
+
] })
|
|
366
|
+
] }),
|
|
367
|
+
visible.length > 0 && /* @__PURE__ */ jsx(
|
|
368
|
+
TimelineList,
|
|
369
|
+
{
|
|
370
|
+
items,
|
|
371
|
+
expandedIds,
|
|
372
|
+
renderPreview: (it) => {
|
|
373
|
+
const doc = byId.get(it.id);
|
|
374
|
+
return doc ? /* @__PURE__ */ jsx(DocumentPreview, { document: doc, provider, onOpen: () => onView(doc) }) : null;
|
|
375
|
+
},
|
|
376
|
+
onSelect: (it) => {
|
|
377
|
+
const doc = byId.get(it.id);
|
|
378
|
+
if (doc) onView(doc);
|
|
379
|
+
},
|
|
380
|
+
renderActions: (it) => {
|
|
381
|
+
const doc = byId.get(it.id);
|
|
382
|
+
if (!doc) return null;
|
|
383
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
384
|
+
/* @__PURE__ */ jsx(
|
|
385
|
+
Button,
|
|
386
|
+
{
|
|
387
|
+
variant: "bare",
|
|
388
|
+
size: "bare",
|
|
389
|
+
type: "button",
|
|
390
|
+
onClick: (e) => {
|
|
391
|
+
e.stopPropagation();
|
|
392
|
+
toggleOne(doc.id);
|
|
393
|
+
},
|
|
394
|
+
className: "rounded-md p-1.5 hover:bg-muted",
|
|
395
|
+
"aria-label": t(expandedIds.has(doc.id) ? "customForms.hideContent" : "customForms.showContent"),
|
|
396
|
+
"aria-expanded": expandedIds.has(doc.id),
|
|
397
|
+
children: expandedIds.has(doc.id) ? /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5 text-muted-foreground" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
398
|
+
}
|
|
399
|
+
),
|
|
400
|
+
/* @__PURE__ */ jsx(
|
|
401
|
+
Button,
|
|
402
|
+
{
|
|
403
|
+
variant: "bare",
|
|
404
|
+
size: "bare",
|
|
405
|
+
type: "button",
|
|
406
|
+
onClick: (e) => {
|
|
407
|
+
e.stopPropagation();
|
|
408
|
+
setArchiving(doc);
|
|
409
|
+
},
|
|
410
|
+
className: "rounded-md p-1.5 hover:bg-muted",
|
|
411
|
+
"aria-label": t("customForms.archive"),
|
|
412
|
+
title: t("customForms.archive"),
|
|
413
|
+
children: /* @__PURE__ */ jsx(Archive, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
] });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
),
|
|
420
|
+
visible.length < total && /* @__PURE__ */ jsx("div", { className: "flex justify-center pt-4", children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", disabled: loadingMore, onClick: () => void loadMore(), children: loadingMore ? t("customForms.loadingMore") : t("customForms.loadMore", { count: String(total - visible.length) }) }) })
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
),
|
|
424
|
+
/* @__PURE__ */ jsx(
|
|
425
|
+
ConfirmDialog,
|
|
426
|
+
{
|
|
427
|
+
open: archiving !== null,
|
|
428
|
+
title: t("customForms.archiveTitle"),
|
|
429
|
+
description: t("customForms.archiveDescription", {
|
|
430
|
+
name: archiving?.templateName ?? archiving?.title ?? t("customForms.documentFallbackTitle")
|
|
431
|
+
}),
|
|
432
|
+
confirmLabel: t("customForms.archive"),
|
|
433
|
+
cancelLabel: t("common.cancel"),
|
|
434
|
+
onConfirm: () => void handleArchive(),
|
|
435
|
+
onCancel: () => setArchiving(null)
|
|
436
|
+
}
|
|
437
|
+
)
|
|
438
|
+
] });
|
|
439
|
+
}
|
|
440
|
+
function DocumentBodySkeleton({ rows = 5 }) {
|
|
441
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-4 rounded-xl border p-4", "aria-hidden": true, children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
442
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-28 animate-pulse rounded bg-muted" }),
|
|
443
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 w-full animate-pulse rounded-md bg-muted" })
|
|
444
|
+
] }, i)) });
|
|
445
|
+
}
|
|
446
|
+
function DocumentHeaderSkeleton() {
|
|
447
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", "aria-hidden": true, children: [
|
|
448
|
+
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-3.5 shrink-0 animate-pulse rounded bg-muted" }),
|
|
449
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
|
|
450
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-48 max-w-full animate-pulse rounded bg-muted" }),
|
|
451
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-muted" })
|
|
452
|
+
] })
|
|
453
|
+
] });
|
|
454
|
+
}
|
|
455
|
+
function generateTitle(templateName, personName) {
|
|
456
|
+
const date = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
457
|
+
return personName ? `${templateName} \u2014 ${personName} \u2014 ${date}` : `${templateName} \u2014 ${date}`;
|
|
458
|
+
}
|
|
459
|
+
function DocumentFormDialog({
|
|
460
|
+
template: initialTemplate,
|
|
461
|
+
existingDocument,
|
|
462
|
+
personId,
|
|
463
|
+
personName,
|
|
464
|
+
person,
|
|
465
|
+
provider,
|
|
466
|
+
store,
|
|
467
|
+
onSaved,
|
|
468
|
+
onBack
|
|
469
|
+
}) {
|
|
470
|
+
const t = useTranslation();
|
|
471
|
+
const guardDocuments = useLimitGuard("documents_month");
|
|
472
|
+
const [template, setTemplate] = useState(initialTemplate);
|
|
473
|
+
const [data, setData] = useState(
|
|
474
|
+
() => existingDocument ? existingDocument.data : prefillFromPerson(initialTemplate?.schema, person)
|
|
475
|
+
);
|
|
476
|
+
const [saving, setSaving] = useState(false);
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
if (existingDocument && !template) {
|
|
479
|
+
provider.getTemplateById(existingDocument.templateId).then(setTemplate);
|
|
480
|
+
}
|
|
481
|
+
}, [existingDocument, template, provider]);
|
|
482
|
+
const prefilled = React3.useRef(!!initialTemplate || !!existingDocument);
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (prefilled.current || !template || existingDocument) return;
|
|
485
|
+
prefilled.current = true;
|
|
486
|
+
setData((prev) => prefillFromPerson(template.schema, person, prev));
|
|
487
|
+
}, [template, existingDocument, person]);
|
|
488
|
+
const handleSave = useCallback(
|
|
489
|
+
async (status) => {
|
|
490
|
+
if (!template) return;
|
|
491
|
+
if (!existingDocument && await guardDocuments() === "blocked") return;
|
|
492
|
+
setSaving(true);
|
|
493
|
+
try {
|
|
494
|
+
const autoTitle = generateTitle(template.name, personName);
|
|
495
|
+
if (existingDocument) {
|
|
496
|
+
await store.getState().updateDocument(existingDocument.id, { data, status });
|
|
497
|
+
} else {
|
|
498
|
+
await store.getState().createDocument({
|
|
499
|
+
templateId: template.id,
|
|
500
|
+
personId,
|
|
501
|
+
title: autoTitle,
|
|
502
|
+
data,
|
|
503
|
+
status
|
|
504
|
+
});
|
|
505
|
+
invalidateLimit("documents_month");
|
|
506
|
+
}
|
|
507
|
+
onSaved();
|
|
508
|
+
} finally {
|
|
509
|
+
setSaving(false);
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
[template, existingDocument, data, personId, personName, store, onSaved, guardDocuments]
|
|
513
|
+
);
|
|
514
|
+
const snapshot = React3.useRef(null);
|
|
515
|
+
useEffect(() => {
|
|
516
|
+
if (template && snapshot.current === null) {
|
|
517
|
+
snapshot.current = JSON.stringify(existingDocument?.data ?? {});
|
|
518
|
+
}
|
|
519
|
+
}, [template, existingDocument]);
|
|
520
|
+
const dirty = snapshot.current !== null && JSON.stringify(data) !== snapshot.current;
|
|
521
|
+
useSaveBar({
|
|
522
|
+
dirty,
|
|
523
|
+
saving,
|
|
524
|
+
onSave: () => {
|
|
525
|
+
void handleSave("completed");
|
|
526
|
+
},
|
|
527
|
+
onDiscard: () => onBack(),
|
|
528
|
+
saveLabel: t("customForms.saveAndComplete")
|
|
529
|
+
});
|
|
530
|
+
if (!template) {
|
|
531
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
532
|
+
existingDocument?.title ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
533
|
+
/* @__PURE__ */ jsx(
|
|
534
|
+
Button,
|
|
535
|
+
{
|
|
536
|
+
variant: "bare",
|
|
537
|
+
size: "bare",
|
|
538
|
+
onClick: onBack,
|
|
539
|
+
className: "flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground",
|
|
540
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
|
|
541
|
+
}
|
|
542
|
+
),
|
|
543
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
544
|
+
/* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold", children: existingDocument.title }),
|
|
545
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: new Date(existingDocument.createdAt).toLocaleDateString() })
|
|
546
|
+
] })
|
|
547
|
+
] }) : /* @__PURE__ */ jsx(DocumentHeaderSkeleton, {}),
|
|
548
|
+
/* @__PURE__ */ jsx(
|
|
549
|
+
DocumentBodySkeleton,
|
|
550
|
+
{
|
|
551
|
+
rows: Math.min(Math.max(Object.keys(existingDocument?.data ?? {}).length, 3), 8)
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
] });
|
|
555
|
+
}
|
|
556
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
557
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
558
|
+
/* @__PURE__ */ jsx(
|
|
559
|
+
Button,
|
|
560
|
+
{
|
|
561
|
+
variant: "bare",
|
|
562
|
+
size: "bare",
|
|
563
|
+
onClick: onBack,
|
|
564
|
+
className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
|
|
565
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
|
|
566
|
+
}
|
|
567
|
+
),
|
|
568
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
569
|
+
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm", children: template.name }),
|
|
570
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
571
|
+
personName && /* @__PURE__ */ jsxs("span", { children: [
|
|
572
|
+
personName,
|
|
573
|
+
" \xB7 "
|
|
574
|
+
] }),
|
|
575
|
+
(/* @__PURE__ */ new Date()).toLocaleDateString()
|
|
576
|
+
] })
|
|
577
|
+
] })
|
|
578
|
+
] }),
|
|
579
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-xl border p-4", children: /* @__PURE__ */ jsx(
|
|
580
|
+
FormRenderer,
|
|
581
|
+
{
|
|
582
|
+
schema: template.schema,
|
|
583
|
+
data,
|
|
584
|
+
onChange: setData
|
|
585
|
+
}
|
|
586
|
+
) })
|
|
587
|
+
] });
|
|
588
|
+
}
|
|
589
|
+
var STATUS_COLORS = {
|
|
590
|
+
draft: "bg-warning-soft text-warning-soft-foreground",
|
|
591
|
+
completed: "bg-success-soft text-success-soft-foreground",
|
|
592
|
+
signed: "bg-info-soft text-info-soft-foreground",
|
|
593
|
+
archived: "bg-muted text-muted-foreground"
|
|
594
|
+
};
|
|
595
|
+
function FormViewer({ document: doc, provider, onBack, onEdit }) {
|
|
596
|
+
const t = useTranslation();
|
|
597
|
+
const [template, setTemplate] = useState(null);
|
|
598
|
+
useEffect(() => {
|
|
599
|
+
if (!doc.templateId) return;
|
|
600
|
+
const timer = setTimeout(() => {
|
|
601
|
+
provider.getTemplateById(doc.templateId).then(setTemplate);
|
|
602
|
+
}, 50);
|
|
603
|
+
return () => clearTimeout(timer);
|
|
604
|
+
}, [doc.templateId, provider]);
|
|
605
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
606
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
607
|
+
/* @__PURE__ */ jsx(
|
|
608
|
+
Button,
|
|
609
|
+
{
|
|
610
|
+
variant: "bare",
|
|
611
|
+
size: "bare",
|
|
612
|
+
onClick: onBack,
|
|
613
|
+
className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
|
|
614
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
|
|
615
|
+
}
|
|
616
|
+
),
|
|
617
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
618
|
+
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm truncate", children: doc.title ?? template?.name ?? "" }),
|
|
619
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: new Date(doc.createdAt).toLocaleDateString() })
|
|
620
|
+
] }),
|
|
621
|
+
/* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${STATUS_COLORS[doc.status] ?? ""}`, children: t(`customForms.status.${doc.status}`) }),
|
|
622
|
+
/* @__PURE__ */ jsxs(
|
|
623
|
+
Button,
|
|
624
|
+
{
|
|
625
|
+
variant: "outline",
|
|
626
|
+
size: "sm",
|
|
627
|
+
className: "h-8",
|
|
628
|
+
disabled: !template,
|
|
629
|
+
onClick: () => template && printDocument(template, doc),
|
|
630
|
+
children: [
|
|
631
|
+
/* @__PURE__ */ jsx(Printer, { className: "h-3 w-3 mr-1" }),
|
|
632
|
+
t("customForms.printPdf")
|
|
633
|
+
]
|
|
634
|
+
}
|
|
635
|
+
),
|
|
636
|
+
onEdit && doc.status === "draft" && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => onEdit(doc), children: [
|
|
637
|
+
/* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3 mr-1" }),
|
|
638
|
+
t("common.edit")
|
|
639
|
+
] })
|
|
640
|
+
] }),
|
|
641
|
+
template ? /* @__PURE__ */ jsx("div", { className: "rounded-xl border p-4", children: /* @__PURE__ */ jsx(
|
|
642
|
+
FormRenderer,
|
|
643
|
+
{
|
|
644
|
+
schema: template.schema,
|
|
645
|
+
data: doc.data,
|
|
646
|
+
onChange: () => {
|
|
647
|
+
},
|
|
648
|
+
readOnly: true
|
|
649
|
+
}
|
|
650
|
+
) }) : (
|
|
651
|
+
// O documento já diz quantos campos tem: o esqueleto sai da altura certa
|
|
652
|
+
// em vez de chutar, e a chegada do conteúdo não mexe no scroll.
|
|
653
|
+
/* @__PURE__ */ jsx(DocumentBodySkeleton, { rows: Math.min(Math.max(Object.keys(doc.data ?? {}).length, 3), 8) })
|
|
654
|
+
)
|
|
655
|
+
] });
|
|
656
|
+
}
|
|
657
|
+
var SEARCH_THRESHOLD = 8;
|
|
658
|
+
var normalize = (s) => s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
659
|
+
function AddDocumentDropdown({ personId, onSelect, optionFilter, label }) {
|
|
660
|
+
const t = useTranslation();
|
|
661
|
+
const [open, setOpen] = useState(false);
|
|
662
|
+
const [options, setOptions] = useState([]);
|
|
663
|
+
const [loaded, setLoaded] = useState(false);
|
|
664
|
+
const [query, setQuery] = useState("");
|
|
665
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
666
|
+
const loadingRef = useRef(false);
|
|
667
|
+
const inputRef = useRef(null);
|
|
668
|
+
const listRef = useRef(null);
|
|
669
|
+
const handleOpenChange = useCallback((next) => {
|
|
670
|
+
setOpen(next);
|
|
671
|
+
if (!next) return;
|
|
672
|
+
setQuery("");
|
|
673
|
+
setActiveIndex(0);
|
|
674
|
+
if (loaded || loadingRef.current) return;
|
|
675
|
+
loadingRef.current = true;
|
|
676
|
+
getAllDocumentTypes(personId).then((opts) => {
|
|
677
|
+
setOptions(optionFilter ? opts.filter(optionFilter) : opts);
|
|
678
|
+
setLoaded(true);
|
|
679
|
+
loadingRef.current = false;
|
|
680
|
+
});
|
|
681
|
+
}, [personId, loaded, optionFilter]);
|
|
682
|
+
const filtered = useMemo(() => {
|
|
683
|
+
const q = normalize(query.trim());
|
|
684
|
+
if (!q) return options;
|
|
685
|
+
return options.filter(
|
|
686
|
+
(o) => normalize(o.label).includes(q) || (o.description ? normalize(o.description).includes(q) : false)
|
|
687
|
+
);
|
|
688
|
+
}, [options, query]);
|
|
689
|
+
const groups = useMemo(() => {
|
|
690
|
+
const acc = [];
|
|
691
|
+
for (const opt of filtered) {
|
|
692
|
+
let group = acc.find((g) => g.label === opt.group);
|
|
693
|
+
if (!group) {
|
|
694
|
+
group = { label: opt.group, items: [] };
|
|
695
|
+
acc.push(group);
|
|
696
|
+
}
|
|
697
|
+
group.items.push(opt);
|
|
698
|
+
}
|
|
699
|
+
return acc;
|
|
700
|
+
}, [filtered]);
|
|
701
|
+
const showSearch = loaded && options.length > SEARCH_THRESHOLD;
|
|
702
|
+
useEffect(() => {
|
|
703
|
+
if (!open || !showSearch) return;
|
|
704
|
+
const id = requestAnimationFrame(() => inputRef.current?.focus());
|
|
705
|
+
return () => cancelAnimationFrame(id);
|
|
706
|
+
}, [open, showSearch]);
|
|
707
|
+
useEffect(() => {
|
|
708
|
+
listRef.current?.querySelector(`[data-index="${activeIndex}"]`)?.scrollIntoView({ block: "nearest" });
|
|
709
|
+
}, [activeIndex]);
|
|
710
|
+
const choose = useCallback((opt) => {
|
|
711
|
+
setOpen(false);
|
|
712
|
+
onSelect(opt);
|
|
713
|
+
}, [onSelect]);
|
|
714
|
+
const handleInputKeyDown = (e) => {
|
|
715
|
+
if (e.key === "Escape") return;
|
|
716
|
+
if (e.key === "ArrowDown") {
|
|
717
|
+
e.preventDefault();
|
|
718
|
+
setActiveIndex((i) => Math.min(filtered.length - 1, i + 1));
|
|
719
|
+
} else if (e.key === "ArrowUp") {
|
|
720
|
+
e.preventDefault();
|
|
721
|
+
setActiveIndex((i) => Math.max(0, i - 1));
|
|
722
|
+
} else if (e.key === "Enter") {
|
|
723
|
+
e.preventDefault();
|
|
724
|
+
const opt = filtered[activeIndex];
|
|
725
|
+
if (opt) choose(opt);
|
|
726
|
+
}
|
|
727
|
+
e.stopPropagation();
|
|
728
|
+
};
|
|
729
|
+
let flatIndex = -1;
|
|
730
|
+
return /* @__PURE__ */ jsxs(Dropdown, { open, onOpenChange: handleOpenChange, children: [
|
|
731
|
+
/* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { size: "sm", className: "h-8", children: [
|
|
732
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 mr-1" }),
|
|
733
|
+
label ?? t("customForms.addDocument"),
|
|
734
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 ml-1 opacity-50" })
|
|
735
|
+
] }) }),
|
|
736
|
+
/* @__PURE__ */ jsxs(DropdownContent, { align: "end", className: "w-72 p-0", children: [
|
|
737
|
+
showSearch && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-2.5 py-2", children: [
|
|
738
|
+
/* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
739
|
+
/* @__PURE__ */ jsx(
|
|
740
|
+
Input,
|
|
741
|
+
{
|
|
742
|
+
ref: inputRef,
|
|
743
|
+
value: query,
|
|
744
|
+
onChange: (e) => {
|
|
745
|
+
setQuery(e.target.value);
|
|
746
|
+
setActiveIndex(0);
|
|
747
|
+
},
|
|
748
|
+
onKeyDown: handleInputKeyDown,
|
|
749
|
+
placeholder: t("customForms.searchDocumentTypes"),
|
|
750
|
+
className: "h-auto rounded-none border-0 bg-transparent p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
751
|
+
}
|
|
752
|
+
)
|
|
753
|
+
] }),
|
|
754
|
+
/* @__PURE__ */ jsxs("div", { ref: listRef, className: "max-h-[min(60vh,20rem)] overflow-y-auto overscroll-contain p-1", children: [
|
|
755
|
+
groups.map((group, gi) => /* @__PURE__ */ jsxs(React3.Fragment, { children: [
|
|
756
|
+
gi > 0 && /* @__PURE__ */ jsx(DropdownSeparator, {}),
|
|
757
|
+
/* @__PURE__ */ jsx(DropdownLabel, { className: "text-[10px] uppercase tracking-wider", children: group.label }),
|
|
758
|
+
group.items.map((opt) => {
|
|
759
|
+
const Icon = opt.icon;
|
|
760
|
+
flatIndex += 1;
|
|
761
|
+
const index = flatIndex;
|
|
762
|
+
return /* @__PURE__ */ jsxs(
|
|
763
|
+
Button,
|
|
764
|
+
{
|
|
765
|
+
variant: "bare",
|
|
766
|
+
size: "bare",
|
|
767
|
+
type: "button",
|
|
768
|
+
"data-index": index,
|
|
769
|
+
onClick: () => choose(opt),
|
|
770
|
+
onMouseEnter: () => setActiveIndex(index),
|
|
771
|
+
className: `font-normal justify-start relative flex w-full cursor-default select-none items-center gap-2 rounded-button px-2 py-1.5 text-left text-sm outline-none transition-colors ${index === activeIndex ? "bg-muted" : ""}`,
|
|
772
|
+
children: [
|
|
773
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
774
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
775
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm truncate", children: opt.label }),
|
|
776
|
+
opt.description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground truncate", children: opt.description })
|
|
777
|
+
] })
|
|
778
|
+
]
|
|
779
|
+
},
|
|
780
|
+
opt.id
|
|
781
|
+
);
|
|
782
|
+
})
|
|
783
|
+
] }, group.label)),
|
|
784
|
+
!loaded && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-3", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-4 border-2 border-primary border-t-transparent animate-spin rounded-full" }) }),
|
|
785
|
+
loaded && options.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customForms.noTemplates") }),
|
|
786
|
+
loaded && options.length > 0 && filtered.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customForms.noResults") })
|
|
787
|
+
] })
|
|
788
|
+
] })
|
|
789
|
+
] });
|
|
790
|
+
}
|
|
791
|
+
var SURFACE_KINDS = {
|
|
792
|
+
records: ["form"],
|
|
793
|
+
files: ["image", "attachment"]
|
|
794
|
+
};
|
|
795
|
+
function PersonDocumentsWidget({ item, config, provider, store, surface = "records" }) {
|
|
796
|
+
const t = useTranslation();
|
|
797
|
+
const kinds = SURFACE_KINDS[surface];
|
|
798
|
+
const optionFilter = useCallback(
|
|
799
|
+
(o) => surface === "records" ? o.id.startsWith("template:") : o.id === "core:image" || o.id === "core:attachment",
|
|
800
|
+
[surface]
|
|
801
|
+
);
|
|
802
|
+
const guardDocuments = useLimitGuard("documents_month");
|
|
803
|
+
const [view, setView] = useState({ type: "list" });
|
|
804
|
+
const fileInputRef = useRef(null);
|
|
805
|
+
const [pendingFileAccept, setPendingFileAccept] = useState("*/*");
|
|
806
|
+
const handleDocTypeSelect = useCallback(async (option) => {
|
|
807
|
+
if (option.id.startsWith("template:")) {
|
|
808
|
+
const templateId = option.id.replace("template:", "");
|
|
809
|
+
const template = await provider.getTemplateById(templateId);
|
|
810
|
+
if (template) {
|
|
811
|
+
setView({ type: "fill", template });
|
|
812
|
+
}
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (option.id === "core:image") {
|
|
816
|
+
setPendingFileAccept("image/*");
|
|
817
|
+
setTimeout(() => fileInputRef.current?.click(), 0);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (option.id === "core:attachment") {
|
|
821
|
+
setPendingFileAccept("*/*");
|
|
822
|
+
setTimeout(() => fileInputRef.current?.click(), 0);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
}, [provider]);
|
|
826
|
+
const handleFiles = useCallback(async (files) => {
|
|
827
|
+
if (await guardDocuments(files.length) === "blocked") return;
|
|
828
|
+
for (const file of files) {
|
|
829
|
+
const isImage = file.type.startsWith("image/");
|
|
830
|
+
await store.getState().createDocument({
|
|
831
|
+
kind: isImage ? "image" : "attachment",
|
|
832
|
+
personId: item.id,
|
|
833
|
+
title: file.name,
|
|
834
|
+
fileName: file.name,
|
|
835
|
+
fileSize: file.size,
|
|
836
|
+
mimeType: file.type,
|
|
837
|
+
status: "completed"
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
invalidateLimit("documents_month");
|
|
841
|
+
}, [store, item.id, guardDocuments]);
|
|
842
|
+
const handleFileInputChange = useCallback((e) => {
|
|
843
|
+
const files = Array.from(e.target.files ?? []);
|
|
844
|
+
if (files.length > 0) handleFiles(files);
|
|
845
|
+
e.target.value = "";
|
|
846
|
+
}, [handleFiles]);
|
|
847
|
+
const handleViewDoc = useCallback((doc) => {
|
|
848
|
+
setView({ type: "view", document: doc });
|
|
849
|
+
}, []);
|
|
850
|
+
const handleEditDoc = useCallback((doc) => {
|
|
851
|
+
setView({ type: "edit", document: doc });
|
|
852
|
+
}, []);
|
|
853
|
+
const handleBack = useCallback(() => {
|
|
854
|
+
setView({ type: "list" });
|
|
855
|
+
}, []);
|
|
856
|
+
const handleSaved = useCallback(() => {
|
|
857
|
+
setView({ type: "list" });
|
|
858
|
+
}, []);
|
|
859
|
+
if (view.type === "fill") {
|
|
860
|
+
return /* @__PURE__ */ jsx(
|
|
861
|
+
DocumentFormDialog,
|
|
862
|
+
{
|
|
863
|
+
template: view.template,
|
|
864
|
+
personId: item.id,
|
|
865
|
+
personName: item.name,
|
|
866
|
+
person: item,
|
|
867
|
+
provider,
|
|
868
|
+
store,
|
|
869
|
+
onSaved: handleSaved,
|
|
870
|
+
onBack: handleBack
|
|
871
|
+
}
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
if (view.type === "view") {
|
|
875
|
+
const doc = view.document;
|
|
876
|
+
const isForm = doc.kind === "form" || !doc.kind && doc.templateId;
|
|
877
|
+
if (!isForm) {
|
|
878
|
+
return /* @__PURE__ */ jsx(FileDocumentViewer, { document: doc, onBack: handleBack });
|
|
879
|
+
}
|
|
880
|
+
return /* @__PURE__ */ jsx(
|
|
881
|
+
FormViewer,
|
|
882
|
+
{
|
|
883
|
+
document: doc,
|
|
884
|
+
provider,
|
|
885
|
+
onBack: handleBack,
|
|
886
|
+
onEdit: handleEditDoc
|
|
887
|
+
}
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
if (view.type === "edit") {
|
|
891
|
+
return /* @__PURE__ */ jsx(
|
|
892
|
+
DocumentFormDialog,
|
|
893
|
+
{
|
|
894
|
+
template: null,
|
|
895
|
+
existingDocument: view.document,
|
|
896
|
+
personId: item.id,
|
|
897
|
+
personName: item.name,
|
|
898
|
+
person: item,
|
|
899
|
+
provider,
|
|
900
|
+
store,
|
|
901
|
+
onSaved: handleSaved,
|
|
902
|
+
onBack: handleBack
|
|
903
|
+
}
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
907
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
908
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t(surface === "files" ? "customForms.filesTitle" : "customForms.recordsTitle") }),
|
|
909
|
+
/* @__PURE__ */ jsx(
|
|
910
|
+
AddDocumentDropdown,
|
|
911
|
+
{
|
|
912
|
+
personId: item.id,
|
|
913
|
+
onSelect: handleDocTypeSelect,
|
|
914
|
+
optionFilter,
|
|
915
|
+
label: t(surface === "files" ? "customForms.addFile" : "customForms.addRecord")
|
|
916
|
+
}
|
|
917
|
+
)
|
|
918
|
+
] }),
|
|
919
|
+
/* @__PURE__ */ jsx(
|
|
920
|
+
DocumentList,
|
|
921
|
+
{
|
|
922
|
+
personId: item.id,
|
|
923
|
+
provider,
|
|
924
|
+
store,
|
|
925
|
+
kinds,
|
|
926
|
+
onView: handleViewDoc,
|
|
927
|
+
onFileDrop: handleFiles
|
|
928
|
+
}
|
|
929
|
+
),
|
|
930
|
+
/* @__PURE__ */ jsx(
|
|
931
|
+
"input",
|
|
932
|
+
{
|
|
933
|
+
ref: fileInputRef,
|
|
934
|
+
type: "file",
|
|
935
|
+
className: "hidden",
|
|
936
|
+
accept: pendingFileAccept,
|
|
937
|
+
multiple: true,
|
|
938
|
+
onChange: handleFileInputChange
|
|
939
|
+
}
|
|
940
|
+
)
|
|
941
|
+
] });
|
|
942
|
+
}
|
|
943
|
+
function FileDocumentViewer({ document: doc, onBack }) {
|
|
944
|
+
const isImage = doc.kind === "image" || doc.mimeType?.startsWith("image/");
|
|
945
|
+
const Icon = isImage ? Image : Paperclip;
|
|
946
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
947
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
948
|
+
/* @__PURE__ */ jsx(
|
|
949
|
+
Button,
|
|
950
|
+
{
|
|
951
|
+
variant: "bare",
|
|
952
|
+
size: "bare",
|
|
953
|
+
onClick: onBack,
|
|
954
|
+
className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
|
|
955
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
|
|
956
|
+
}
|
|
957
|
+
),
|
|
958
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
959
|
+
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm truncate", children: doc.title ?? doc.fileName ?? "Document" }),
|
|
960
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: new Date(doc.createdAt).toLocaleDateString() })
|
|
961
|
+
] }),
|
|
962
|
+
/* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-[10px] bg-success/15 text-success", children: doc.status === "completed" ? "Finalizado" : doc.status })
|
|
963
|
+
] }),
|
|
964
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-xl border p-6 flex flex-col items-center gap-4", children: isImage && doc.fileUrl ? /* @__PURE__ */ jsx("img", { src: doc.fileUrl, alt: doc.fileName ?? "", className: "max-w-full max-h-[500px] rounded-lg object-contain" }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 py-8", children: [
|
|
965
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-16 w-16 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(Icon, { className: "h-7 w-7 text-muted-foreground" }) }),
|
|
966
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: doc.fileName ?? doc.title ?? "Arquivo" }),
|
|
967
|
+
doc.fileSize && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
968
|
+
(doc.fileSize / 1024).toFixed(1),
|
|
969
|
+
" KB"
|
|
970
|
+
] })
|
|
971
|
+
] }) })
|
|
972
|
+
] });
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export { PersonDocumentsWidget };
|
|
976
|
+
//# sourceMappingURL=PersonDocumentsWidget-RGN4UHS3.js.map
|
|
977
|
+
//# sourceMappingURL=PersonDocumentsWidget-RGN4UHS3.js.map
|