@fayz-ai/plugin-forms 0.9.4 → 0.10.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.
- package/dist/components/AddDocumentDropdown.d.ts.map +1 -1
- package/dist/index.js +297 -127
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/store.d.ts +7 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/views/TemplateListView.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddDocumentDropdown.d.ts","sourceRoot":"","sources":["../../src/components/AddDocumentDropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"AddDocumentDropdown.d.ts","sourceRoot":"","sources":["../../src/components/AddDocumentDropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAWhF,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAEpF,UAAU,wBAAwB;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAA;CAC/C;AAQD,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,wBAAwB,qBA6KnF"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createOrUpdateFormTool, prefillFromPerson, FormRenderer, printDocument } from './chunk-RBLXIKWB.js';
|
|
2
|
-
import React2, { useState, useCallback, useRef, useEffect } from 'react';
|
|
3
|
-
import { registerTranslations, createSafeDataProvider, getSupabaseClientOptional, useTranslation, getActiveTenantId } from '@fayz-ai/core';
|
|
4
|
-
import { PluginSettingsPanel, registerTimelineSource, useLimitGuard, invalidateLimit, PermissionGate } from '@fayz-ai/admin';
|
|
2
|
+
import React2, { useState, useCallback, useRef, useMemo, useEffect } from 'react';
|
|
3
|
+
import { registerTranslations, createSafeDataProvider, getSupabaseClientOptional, useTranslation, getActiveTenantId, emitDomainEvent, useDataVersion } from '@fayz-ai/core';
|
|
4
|
+
import { PluginSettingsPanel, registerTimelineSource, useLimitGuard, invalidateLimit, askAI, PermissionGate, QuickActionsButton } from '@fayz-ai/admin';
|
|
5
5
|
import { create } from 'zustand';
|
|
6
|
-
import { FileText, Image, Paperclip,
|
|
7
|
-
import {
|
|
6
|
+
import { FileText, Image, Paperclip, Pencil, Archive, Plus, Search, ArrowLeft, Printer, ChevronDown, Upload, Eye } from 'lucide-react';
|
|
7
|
+
import { Badge, Button, PageHeaderActions, Card, DataTable, useSaveBar, Dropdown, DropdownTrigger, DropdownContent, DropdownSeparator, DropdownLabel } from '@fayz-ai/ui';
|
|
8
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
10
|
// src/data/tables.ts
|
|
@@ -503,6 +503,14 @@ function createSupabaseFormsProvider() {
|
|
|
503
503
|
}
|
|
504
504
|
};
|
|
505
505
|
}
|
|
506
|
+
var FORM_TEMPLATE_EVENT = "forms.template.";
|
|
507
|
+
function emitTemplateEvent(action, id, name) {
|
|
508
|
+
emitDomainEvent(
|
|
509
|
+
`${FORM_TEMPLATE_EVENT}${action}`,
|
|
510
|
+
{ record_id: id, record_name: name ?? null },
|
|
511
|
+
{ tenantId: getActiveTenantId() ?? null, subject: { type: "form_template", id } }
|
|
512
|
+
);
|
|
513
|
+
}
|
|
506
514
|
function createCustomFormsStore(provider) {
|
|
507
515
|
return create((set) => ({
|
|
508
516
|
templates: [],
|
|
@@ -557,6 +565,7 @@ function createCustomFormsStore(provider) {
|
|
|
557
565
|
async createTemplate(input) {
|
|
558
566
|
const template = await provider.createTemplate(input);
|
|
559
567
|
set((s) => ({ templates: [template, ...s.templates], templatesTotal: s.templatesTotal + 1 }));
|
|
568
|
+
emitTemplateEvent("created", template.id, template.name);
|
|
560
569
|
return template;
|
|
561
570
|
},
|
|
562
571
|
async updateTemplate(id, input) {
|
|
@@ -565,10 +574,12 @@ function createCustomFormsStore(provider) {
|
|
|
565
574
|
templates: s.templates.map((t) => t.id === id ? template : t),
|
|
566
575
|
activeTemplate: s.activeTemplate?.id === id ? template : s.activeTemplate
|
|
567
576
|
}));
|
|
577
|
+
emitTemplateEvent("updated", template.id, template.name);
|
|
568
578
|
return template;
|
|
569
579
|
},
|
|
570
580
|
async deleteTemplate(id) {
|
|
571
581
|
await provider.deleteTemplate(id);
|
|
582
|
+
emitTemplateEvent("deleted", id);
|
|
572
583
|
set((s) => ({
|
|
573
584
|
templates: s.templates.filter((t) => t.id !== id),
|
|
574
585
|
templatesTotal: s.templatesTotal - 1
|
|
@@ -628,6 +639,16 @@ var en = {
|
|
|
628
639
|
"customForms.settingsSubtitle": "Create and manage custom forms for your business",
|
|
629
640
|
// Templates
|
|
630
641
|
"customForms.templates": "Templates",
|
|
642
|
+
"customForms.newWithAIHint": "Describe the form and I build the fields.",
|
|
643
|
+
"customForms.newByHand": "Create by hand",
|
|
644
|
+
"customForms.newByHandHint": "Build it field by field in the builder.",
|
|
645
|
+
"customForms.askPlaceholder": "Describe the form\u2026",
|
|
646
|
+
"customForms.askPersona": "Form builder",
|
|
647
|
+
"customForms.askPitch": "Tell me the form you need.",
|
|
648
|
+
"customForms.askDescription": "Describe what it is for and I propose the fields, their types and options in one go. You check and adjust before saving.",
|
|
649
|
+
"customForms.askSuggestion.0": "A full intake form",
|
|
650
|
+
"customForms.askSuggestion.1": "Consent form for an aesthetic procedure",
|
|
651
|
+
"customForms.askSuggestion.2": "Session progress note",
|
|
631
652
|
"customForms.newTemplate": "New Template",
|
|
632
653
|
"customForms.editTemplate": "Edit Template",
|
|
633
654
|
"customForms.templateName": "Form name",
|
|
@@ -691,6 +712,8 @@ var en = {
|
|
|
691
712
|
"customForms.documents": "Documents",
|
|
692
713
|
"customForms.addDocument": "Add Document",
|
|
693
714
|
"customForms.selectTemplate": "Select a form",
|
|
715
|
+
"customForms.searchDocumentTypes": "Search forms or files...",
|
|
716
|
+
"customForms.noResults": "No results",
|
|
694
717
|
"customForms.noDocuments": "No documents yet",
|
|
695
718
|
"customForms.noDocumentsDescription": "Add a document to start recording information.",
|
|
696
719
|
"customForms.documentTitle": "Document title",
|
|
@@ -702,6 +725,10 @@ var en = {
|
|
|
702
725
|
"customForms.printPdf": "Print / PDF",
|
|
703
726
|
"customForms.usageCount": "{{count}} filled in",
|
|
704
727
|
"customForms.usageLast": "last on {{date}}",
|
|
728
|
+
// Template list columns
|
|
729
|
+
"customForms.template": "Template",
|
|
730
|
+
"customForms.categoryColumn": "Category",
|
|
731
|
+
"customForms.usageColumn": "Usage",
|
|
705
732
|
// Registry
|
|
706
733
|
"registry.form-categories": "Categories",
|
|
707
734
|
"registry.form-categories.description": "Categories for organizing form templates",
|
|
@@ -719,6 +746,16 @@ var ptBR = {
|
|
|
719
746
|
"customForms.settingsSubtitle": "Crie e gerencie formul\xE1rios personalizados para o seu neg\xF3cio",
|
|
720
747
|
// Templates
|
|
721
748
|
"customForms.templates": "Modelos",
|
|
749
|
+
"customForms.newWithAIHint": "Descreva o formul\xE1rio e eu monto os campos.",
|
|
750
|
+
"customForms.newByHand": "Criar manualmente",
|
|
751
|
+
"customForms.newByHandHint": "Montar campo a campo no construtor.",
|
|
752
|
+
"customForms.askPlaceholder": "Descreva o formul\xE1rio\u2026",
|
|
753
|
+
"customForms.askPersona": "Montador de formul\xE1rios",
|
|
754
|
+
"customForms.askPitch": "Diga o formul\xE1rio que voc\xEA precisa.",
|
|
755
|
+
"customForms.askDescription": "Descreva para que serve e eu proponho os campos, os tipos e as op\xE7\xF5es de uma vez. Voc\xEA confere e ajusta antes de salvar.",
|
|
756
|
+
"customForms.askSuggestion.0": "Uma ficha de anamnese completa",
|
|
757
|
+
"customForms.askSuggestion.1": "Termo de consentimento para procedimento est\xE9tico",
|
|
758
|
+
"customForms.askSuggestion.2": "Ficha de evolu\xE7\xE3o de atendimento",
|
|
722
759
|
"customForms.newTemplate": "Novo Modelo",
|
|
723
760
|
"customForms.editTemplate": "Editar Modelo",
|
|
724
761
|
"customForms.templateName": "Nome do formul\xE1rio",
|
|
@@ -782,6 +819,8 @@ var ptBR = {
|
|
|
782
819
|
"customForms.documents": "Documentos",
|
|
783
820
|
"customForms.addDocument": "Novo Documento",
|
|
784
821
|
"customForms.selectTemplate": "Selecione um formul\xE1rio",
|
|
822
|
+
"customForms.searchDocumentTypes": "Buscar formul\xE1rio ou arquivo...",
|
|
823
|
+
"customForms.noResults": "Nenhum resultado",
|
|
785
824
|
"customForms.noDocuments": "Nenhum documento",
|
|
786
825
|
"customForms.noDocumentsDescription": "Adicione um documento para come\xE7ar a registrar informa\xE7\xF5es.",
|
|
787
826
|
"customForms.documentTitle": "T\xEDtulo do documento",
|
|
@@ -793,6 +832,10 @@ var ptBR = {
|
|
|
793
832
|
"customForms.printPdf": "Imprimir / PDF",
|
|
794
833
|
"customForms.usageCount": "{{count}} preenchidos",
|
|
795
834
|
"customForms.usageLast": "\xFAltimo em {{date}}",
|
|
835
|
+
// Colunas da lista de modelos
|
|
836
|
+
"customForms.template": "Modelo",
|
|
837
|
+
"customForms.categoryColumn": "Categoria",
|
|
838
|
+
"customForms.usageColumn": "Uso",
|
|
796
839
|
// Registry
|
|
797
840
|
"registry.form-categories": "Categorias",
|
|
798
841
|
"registry.form-categories.description": "Categorias para organizar modelos de formul\xE1rios",
|
|
@@ -838,13 +881,40 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
838
881
|
const loading = store((s) => s.templatesLoading);
|
|
839
882
|
const usage = store((s) => s.templateUsage);
|
|
840
883
|
const orgId = getFormsTenantId();
|
|
884
|
+
const createActions = useMemo(() => [
|
|
885
|
+
{
|
|
886
|
+
id: "with-ai",
|
|
887
|
+
label: t("assistant.askAI"),
|
|
888
|
+
description: t("customForms.newWithAIHint"),
|
|
889
|
+
icon: "WandSparkles",
|
|
890
|
+
highlight: true,
|
|
891
|
+
action: () => askAI({
|
|
892
|
+
hint: t("customForms.askPlaceholder"),
|
|
893
|
+
intro: {
|
|
894
|
+
role: "setup",
|
|
895
|
+
persona: t("customForms.askPersona"),
|
|
896
|
+
pitch: t("customForms.askPitch"),
|
|
897
|
+
description: t("customForms.askDescription"),
|
|
898
|
+
suggestions: [0, 1, 2].map((i) => ({ label: t(`customForms.askSuggestion.${i}`) }))
|
|
899
|
+
}
|
|
900
|
+
})
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
id: "by-hand",
|
|
904
|
+
label: t("customForms.newByHand"),
|
|
905
|
+
description: t("customForms.newByHandHint"),
|
|
906
|
+
icon: "SlidersHorizontal",
|
|
907
|
+
action: onNew
|
|
908
|
+
}
|
|
909
|
+
], [t, onNew]);
|
|
910
|
+
const version = useDataVersion(`${FORM_TEMPLATE_EVENT}*`);
|
|
841
911
|
useEffect(() => {
|
|
842
912
|
if (!orgId) return;
|
|
843
913
|
const timer = setTimeout(() => {
|
|
844
914
|
store.getState().fetchTemplates({ search: search || void 0 });
|
|
845
915
|
}, 50);
|
|
846
916
|
return () => clearTimeout(timer);
|
|
847
|
-
}, [orgId, search]);
|
|
917
|
+
}, [orgId, search, version]);
|
|
848
918
|
useEffect(() => {
|
|
849
919
|
if (!orgId) return;
|
|
850
920
|
void store.getState().fetchTemplateUsage();
|
|
@@ -855,106 +925,123 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
855
925
|
}, [store, t]);
|
|
856
926
|
const visibleTemplates = templates.filter((tpl) => tpl.isActive !== false);
|
|
857
927
|
const categoryLabel = (cat) => t(`customForms.category.${cat}`);
|
|
928
|
+
const columns = useMemo(() => [
|
|
929
|
+
{
|
|
930
|
+
id: "name",
|
|
931
|
+
header: t("customForms.template"),
|
|
932
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
933
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: row.original.name }),
|
|
934
|
+
row.original.description && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: row.original.description })
|
|
935
|
+
] })
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
id: "category",
|
|
939
|
+
header: t("customForms.categoryColumn"),
|
|
940
|
+
cell: ({ row }) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${CATEGORY_COLORS[row.original.category]}`, children: categoryLabel(row.original.category) })
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
id: "fields",
|
|
944
|
+
header: t("customForms.builder.fields"),
|
|
945
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("span", { className: "tabular-nums text-muted-foreground", children: [
|
|
946
|
+
row.original.schema.fields.length,
|
|
947
|
+
row.original.version > 1 && /* @__PURE__ */ jsxs("span", { className: "ml-2", children: [
|
|
948
|
+
"v",
|
|
949
|
+
row.original.version
|
|
950
|
+
] })
|
|
951
|
+
] })
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
id: "usage",
|
|
955
|
+
header: t("customForms.usageColumn"),
|
|
956
|
+
// "0 preenchimentos" é informação, não ausência dela: é o que separa um
|
|
957
|
+
// formulário que o salão usa todo dia de um que sobrou de uma importação.
|
|
958
|
+
cell: ({ row }) => {
|
|
959
|
+
const u = usage[row.original.id];
|
|
960
|
+
return /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
961
|
+
t("customForms.usageCount", { count: String(u?.documentCount ?? 0) }),
|
|
962
|
+
u?.lastDocumentAt && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
963
|
+
/* @__PURE__ */ jsx("span", { className: "px-1", children: "\xB7" }),
|
|
964
|
+
t("customForms.usageLast", { date: new Date(u.lastDocumentAt).toLocaleDateString() })
|
|
965
|
+
] })
|
|
966
|
+
] });
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
id: "actions",
|
|
971
|
+
header: "",
|
|
972
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", children: [
|
|
973
|
+
/* @__PURE__ */ jsx(
|
|
974
|
+
"button",
|
|
975
|
+
{
|
|
976
|
+
onClick: (e) => {
|
|
977
|
+
e.stopPropagation();
|
|
978
|
+
onEdit(row.original);
|
|
979
|
+
},
|
|
980
|
+
className: "rounded-md p-1.5 hover:bg-muted",
|
|
981
|
+
title: t("common.edit"),
|
|
982
|
+
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
983
|
+
}
|
|
984
|
+
),
|
|
985
|
+
/* @__PURE__ */ jsx(
|
|
986
|
+
"button",
|
|
987
|
+
{
|
|
988
|
+
onClick: (e) => {
|
|
989
|
+
e.stopPropagation();
|
|
990
|
+
void handleArchive(row.original);
|
|
991
|
+
},
|
|
992
|
+
className: "rounded-md p-1.5 hover:bg-muted",
|
|
993
|
+
title: t("customForms.archive"),
|
|
994
|
+
children: /* @__PURE__ */ jsx(Archive, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
995
|
+
}
|
|
996
|
+
)
|
|
997
|
+
] })
|
|
998
|
+
}
|
|
999
|
+
], [t, usage, onEdit, handleArchive]);
|
|
1000
|
+
const emptyState = /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-6 text-center", children: [
|
|
1001
|
+
/* @__PURE__ */ jsx("div", { className: "mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 text-muted-foreground" }) }),
|
|
1002
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: t("customForms.noTemplates") }),
|
|
1003
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: t("customForms.noTemplatesDescription") }),
|
|
1004
|
+
/* @__PURE__ */ jsx(PermissionGate, { feature: "custom_forms", action: "create", children: /* @__PURE__ */ jsxs(Button, { onClick: onNew, size: "sm", className: "mt-4", children: [
|
|
1005
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1006
|
+
t("customForms.newTemplate")
|
|
1007
|
+
] }) })
|
|
1008
|
+
] });
|
|
858
1009
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
859
|
-
/* @__PURE__ */
|
|
860
|
-
|
|
861
|
-
|
|
1010
|
+
/* @__PURE__ */ jsx(PermissionGate, { feature: "custom_forms", action: "create", children: /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(
|
|
1011
|
+
QuickActionsButton,
|
|
1012
|
+
{
|
|
1013
|
+
label: t("customForms.newTemplate"),
|
|
1014
|
+
variant: "default",
|
|
1015
|
+
actions: createActions
|
|
1016
|
+
}
|
|
1017
|
+
) }) }),
|
|
1018
|
+
/* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
1019
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs("div", { className: "relative min-w-[10rem] flex-1", children: [
|
|
1020
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
862
1021
|
/* @__PURE__ */ jsx(
|
|
863
|
-
|
|
1022
|
+
"input",
|
|
864
1023
|
{
|
|
1024
|
+
type: "text",
|
|
865
1025
|
value: search,
|
|
866
1026
|
onChange: (e) => setSearch(e.target.value),
|
|
867
1027
|
placeholder: t("common.search"),
|
|
868
|
-
className: "
|
|
1028
|
+
className: "w-full bg-transparent py-1.5 pl-8 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none"
|
|
869
1029
|
}
|
|
870
1030
|
)
|
|
871
|
-
] }),
|
|
872
|
-
/* @__PURE__ */ jsx(
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
] }) }),
|
|
886
|
-
visibleTemplates.length > 0 && /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: visibleTemplates.map((template) => /* @__PURE__ */ jsx(
|
|
887
|
-
Card,
|
|
888
|
-
{
|
|
889
|
-
className: "group cursor-pointer hover:shadow-md transition-shadow",
|
|
890
|
-
onClick: () => onEdit(template),
|
|
891
|
-
children: /* @__PURE__ */ jsxs(CardContent, { className: "p-4", children: [
|
|
892
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
893
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
894
|
-
/* @__PURE__ */ jsx("h3", { className: "font-medium text-sm truncate", children: template.name }),
|
|
895
|
-
template.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 line-clamp-2", children: template.description })
|
|
896
|
-
] }),
|
|
897
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity", children: [
|
|
898
|
-
/* @__PURE__ */ jsx(
|
|
899
|
-
"button",
|
|
900
|
-
{
|
|
901
|
-
onClick: (e) => {
|
|
902
|
-
e.stopPropagation();
|
|
903
|
-
onEdit(template);
|
|
904
|
-
},
|
|
905
|
-
className: "p-1.5 rounded-md hover:bg-muted",
|
|
906
|
-
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
907
|
-
}
|
|
908
|
-
),
|
|
909
|
-
/* @__PURE__ */ jsx(
|
|
910
|
-
"button",
|
|
911
|
-
{
|
|
912
|
-
onClick: (e) => {
|
|
913
|
-
e.stopPropagation();
|
|
914
|
-
handleArchive(template);
|
|
915
|
-
},
|
|
916
|
-
className: "p-1.5 rounded-md hover:bg-muted",
|
|
917
|
-
title: t("customForms.archive"),
|
|
918
|
-
children: /* @__PURE__ */ jsx(Archive, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
919
|
-
}
|
|
920
|
-
)
|
|
921
|
-
] })
|
|
922
|
-
] }),
|
|
923
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-3", children: [
|
|
924
|
-
/* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${CATEGORY_COLORS[template.category]}`, children: categoryLabel(template.category) }),
|
|
925
|
-
/* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
926
|
-
template.schema.fields.length,
|
|
927
|
-
" ",
|
|
928
|
-
t("customForms.builder.fields").toLowerCase()
|
|
929
|
-
] }),
|
|
930
|
-
template.version > 1 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
931
|
-
"v",
|
|
932
|
-
template.version
|
|
933
|
-
] })
|
|
934
|
-
] }),
|
|
935
|
-
/* @__PURE__ */ jsxs("p", { className: "mt-2 text-[10px] text-muted-foreground", children: [
|
|
936
|
-
t("customForms.usageCount", {
|
|
937
|
-
count: String(usage[template.id]?.documentCount ?? 0)
|
|
938
|
-
}),
|
|
939
|
-
usage[template.id]?.lastDocumentAt && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
940
|
-
/* @__PURE__ */ jsx("span", { className: "px-1", children: "\xB7" }),
|
|
941
|
-
t("customForms.usageLast", {
|
|
942
|
-
date: new Date(usage[template.id].lastDocumentAt).toLocaleDateString()
|
|
943
|
-
})
|
|
944
|
-
] })
|
|
945
|
-
] })
|
|
946
|
-
] })
|
|
947
|
-
},
|
|
948
|
-
template.id
|
|
949
|
-
)) }),
|
|
950
|
-
loading && /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "p-4 space-y-3", children: [
|
|
951
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-2/3 bg-muted animate-pulse rounded" }),
|
|
952
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-full bg-muted animate-pulse rounded" }),
|
|
953
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
954
|
-
/* @__PURE__ */ jsx("div", { className: "h-5 w-16 bg-muted animate-pulse rounded-full" }),
|
|
955
|
-
/* @__PURE__ */ jsx("div", { className: "h-5 w-12 bg-muted animate-pulse rounded-full" })
|
|
956
|
-
] })
|
|
957
|
-
] }) }, i)) })
|
|
1031
|
+
] }) }),
|
|
1032
|
+
/* @__PURE__ */ jsx(
|
|
1033
|
+
DataTable,
|
|
1034
|
+
{
|
|
1035
|
+
variant: "bare",
|
|
1036
|
+
columns,
|
|
1037
|
+
data: loading ? [] : visibleTemplates,
|
|
1038
|
+
loading,
|
|
1039
|
+
skeletonRows: 3,
|
|
1040
|
+
emptyMessage: emptyState,
|
|
1041
|
+
onRowClick: (row) => onEdit(row)
|
|
1042
|
+
}
|
|
1043
|
+
)
|
|
1044
|
+
] })
|
|
958
1045
|
] });
|
|
959
1046
|
}
|
|
960
1047
|
var ROUTE_BASE = "/settings/custom_forms";
|
|
@@ -1431,13 +1518,24 @@ async function getAllDocumentTypes(personId) {
|
|
|
1431
1518
|
all.sort((a, b) => (a.groupOrder ?? 50) - (b.groupOrder ?? 50) || (a.order ?? 0) - (b.order ?? 0));
|
|
1432
1519
|
return all;
|
|
1433
1520
|
}
|
|
1521
|
+
var SEARCH_THRESHOLD = 8;
|
|
1522
|
+
var normalize = (s) => s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
1434
1523
|
function AddDocumentDropdown({ personId, onSelect }) {
|
|
1435
1524
|
const t = useTranslation();
|
|
1525
|
+
const [open, setOpen] = useState(false);
|
|
1436
1526
|
const [options, setOptions] = useState([]);
|
|
1437
1527
|
const [loaded, setLoaded] = useState(false);
|
|
1528
|
+
const [query, setQuery] = useState("");
|
|
1529
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
1438
1530
|
const loadingRef = useRef(false);
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1531
|
+
const inputRef = useRef(null);
|
|
1532
|
+
const listRef = useRef(null);
|
|
1533
|
+
const handleOpenChange = useCallback((next) => {
|
|
1534
|
+
setOpen(next);
|
|
1535
|
+
if (!next) return;
|
|
1536
|
+
setQuery("");
|
|
1537
|
+
setActiveIndex(0);
|
|
1538
|
+
if (loaded || loadingRef.current) return;
|
|
1441
1539
|
loadingRef.current = true;
|
|
1442
1540
|
getAllDocumentTypes(personId).then((opts) => {
|
|
1443
1541
|
setOptions(opts);
|
|
@@ -1445,38 +1543,110 @@ function AddDocumentDropdown({ personId, onSelect }) {
|
|
|
1445
1543
|
loadingRef.current = false;
|
|
1446
1544
|
});
|
|
1447
1545
|
}, [personId, loaded]);
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1546
|
+
const filtered = useMemo(() => {
|
|
1547
|
+
const q = normalize(query.trim());
|
|
1548
|
+
if (!q) return options;
|
|
1549
|
+
return options.filter(
|
|
1550
|
+
(o) => normalize(o.label).includes(q) || (o.description ? normalize(o.description).includes(q) : false)
|
|
1551
|
+
);
|
|
1552
|
+
}, [options, query]);
|
|
1553
|
+
const groups = useMemo(() => {
|
|
1554
|
+
const acc = [];
|
|
1555
|
+
for (const opt of filtered) {
|
|
1556
|
+
let group = acc.find((g) => g.label === opt.group);
|
|
1557
|
+
if (!group) {
|
|
1558
|
+
group = { label: opt.group, items: [] };
|
|
1559
|
+
acc.push(group);
|
|
1560
|
+
}
|
|
1561
|
+
group.items.push(opt);
|
|
1454
1562
|
}
|
|
1455
|
-
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1563
|
+
return acc;
|
|
1564
|
+
}, [filtered]);
|
|
1565
|
+
const showSearch = loaded && options.length > SEARCH_THRESHOLD;
|
|
1566
|
+
useEffect(() => {
|
|
1567
|
+
if (!open || !showSearch) return;
|
|
1568
|
+
const id = requestAnimationFrame(() => inputRef.current?.focus());
|
|
1569
|
+
return () => cancelAnimationFrame(id);
|
|
1570
|
+
}, [open, showSearch]);
|
|
1571
|
+
useEffect(() => {
|
|
1572
|
+
listRef.current?.querySelector(`[data-index="${activeIndex}"]`)?.scrollIntoView({ block: "nearest" });
|
|
1573
|
+
}, [activeIndex]);
|
|
1574
|
+
const choose = useCallback((opt) => {
|
|
1575
|
+
setOpen(false);
|
|
1576
|
+
onSelect(opt);
|
|
1577
|
+
}, [onSelect]);
|
|
1578
|
+
const handleInputKeyDown = (e) => {
|
|
1579
|
+
if (e.key === "Escape") return;
|
|
1580
|
+
if (e.key === "ArrowDown") {
|
|
1581
|
+
e.preventDefault();
|
|
1582
|
+
setActiveIndex((i) => Math.min(filtered.length - 1, i + 1));
|
|
1583
|
+
} else if (e.key === "ArrowUp") {
|
|
1584
|
+
e.preventDefault();
|
|
1585
|
+
setActiveIndex((i) => Math.max(0, i - 1));
|
|
1586
|
+
} else if (e.key === "Enter") {
|
|
1587
|
+
e.preventDefault();
|
|
1588
|
+
const opt = filtered[activeIndex];
|
|
1589
|
+
if (opt) choose(opt);
|
|
1590
|
+
}
|
|
1591
|
+
e.stopPropagation();
|
|
1592
|
+
};
|
|
1593
|
+
let flatIndex = -1;
|
|
1594
|
+
return /* @__PURE__ */ jsxs(Dropdown, { open, onOpenChange: handleOpenChange, children: [
|
|
1458
1595
|
/* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { size: "sm", className: "h-8", children: [
|
|
1459
1596
|
/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 mr-1" }),
|
|
1460
1597
|
t("customForms.addDocument"),
|
|
1461
1598
|
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 ml-1 opacity-50" })
|
|
1462
1599
|
] }) }),
|
|
1463
|
-
/* @__PURE__ */ jsxs(DropdownContent, { align: "end", className: "w-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
/* @__PURE__ */ jsx(
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1600
|
+
/* @__PURE__ */ jsxs(DropdownContent, { align: "end", className: "w-72 p-0", children: [
|
|
1601
|
+
showSearch && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-2.5 py-2", children: [
|
|
1602
|
+
/* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
1603
|
+
/* @__PURE__ */ jsx(
|
|
1604
|
+
"input",
|
|
1605
|
+
{
|
|
1606
|
+
ref: inputRef,
|
|
1607
|
+
value: query,
|
|
1608
|
+
onChange: (e) => {
|
|
1609
|
+
setQuery(e.target.value);
|
|
1610
|
+
setActiveIndex(0);
|
|
1611
|
+
},
|
|
1612
|
+
onKeyDown: handleInputKeyDown,
|
|
1613
|
+
placeholder: t("customForms.searchDocumentTypes"),
|
|
1614
|
+
className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
1615
|
+
}
|
|
1616
|
+
)
|
|
1617
|
+
] }),
|
|
1618
|
+
/* @__PURE__ */ jsxs("div", { ref: listRef, className: "max-h-[min(60vh,20rem)] overflow-y-auto overscroll-contain p-1", children: [
|
|
1619
|
+
groups.map((group, gi) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
|
|
1620
|
+
gi > 0 && /* @__PURE__ */ jsx(DropdownSeparator, {}),
|
|
1621
|
+
/* @__PURE__ */ jsx(DropdownLabel, { className: "text-[10px] uppercase tracking-wider", children: group.label }),
|
|
1622
|
+
group.items.map((opt) => {
|
|
1623
|
+
const Icon = opt.icon;
|
|
1624
|
+
flatIndex += 1;
|
|
1625
|
+
const index = flatIndex;
|
|
1626
|
+
return /* @__PURE__ */ jsxs(
|
|
1627
|
+
"button",
|
|
1628
|
+
{
|
|
1629
|
+
type: "button",
|
|
1630
|
+
"data-index": index,
|
|
1631
|
+
onClick: () => choose(opt),
|
|
1632
|
+
onMouseEnter: () => setActiveIndex(index),
|
|
1633
|
+
className: `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" : ""}`,
|
|
1634
|
+
children: [
|
|
1635
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1636
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
1637
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm truncate", children: opt.label }),
|
|
1638
|
+
opt.description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground truncate", children: opt.description })
|
|
1639
|
+
] })
|
|
1640
|
+
]
|
|
1641
|
+
},
|
|
1642
|
+
opt.id
|
|
1643
|
+
);
|
|
1644
|
+
})
|
|
1645
|
+
] }, group.label)),
|
|
1646
|
+
!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" }) }),
|
|
1647
|
+
loaded && options.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customForms.noTemplates") }),
|
|
1648
|
+
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") })
|
|
1649
|
+
] })
|
|
1480
1650
|
] })
|
|
1481
1651
|
] });
|
|
1482
1652
|
}
|
|
@@ -1747,7 +1917,6 @@ function createCustomFormsPlugin(options) {
|
|
|
1747
1917
|
const registries = [...formRegistries, ...options?.settingsRegistries ?? []];
|
|
1748
1918
|
const SettingsComponent = () => React2.createElement(PluginSettingsPanel, {
|
|
1749
1919
|
title: config.labels.settingsLabel,
|
|
1750
|
-
subtitle: config.labels.settingsSubtitle,
|
|
1751
1920
|
customTabs: [
|
|
1752
1921
|
{
|
|
1753
1922
|
id: "forms",
|
|
@@ -1765,6 +1934,7 @@ function createCustomFormsPlugin(options) {
|
|
|
1765
1934
|
PersonDocumentsWidget2.displayName = "PersonDocumentsWidget";
|
|
1766
1935
|
return {
|
|
1767
1936
|
id: "custom_forms",
|
|
1937
|
+
defaultAgentRole: "setup",
|
|
1768
1938
|
name: config.labels.pageTitle,
|
|
1769
1939
|
icon: "FileText",
|
|
1770
1940
|
version: "1.0.0",
|