@fayz-ai/plugin-forms 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/CustomFormsContext.d.ts +15 -0
  2. package/dist/CustomFormsContext.d.ts.map +1 -0
  3. package/dist/FormBuilder-4VKYVZAC.cjs +619 -0
  4. package/dist/FormBuilder-4VKYVZAC.cjs.map +1 -0
  5. package/dist/FormBuilder-KDANEL6Z.js +613 -0
  6. package/dist/FormBuilder-KDANEL6Z.js.map +1 -0
  7. package/dist/chunk-32I43T37.js +198 -0
  8. package/dist/chunk-32I43T37.js.map +1 -0
  9. package/dist/chunk-XLUULIVL.cjs +200 -0
  10. package/dist/chunk-XLUULIVL.cjs.map +1 -0
  11. package/dist/components/AddDocumentDropdown.d.ts +9 -0
  12. package/dist/components/AddDocumentDropdown.d.ts.map +1 -0
  13. package/dist/components/BuilderCanvas.d.ts +12 -0
  14. package/dist/components/BuilderCanvas.d.ts.map +1 -0
  15. package/dist/components/BuilderField.d.ts +11 -0
  16. package/dist/components/BuilderField.d.ts.map +1 -0
  17. package/dist/components/DocumentFormDialog.d.ts +17 -0
  18. package/dist/components/DocumentFormDialog.d.ts.map +1 -0
  19. package/dist/components/DocumentList.d.ts +14 -0
  20. package/dist/components/DocumentList.d.ts.map +1 -0
  21. package/dist/components/FieldConfigDialog.d.ts +12 -0
  22. package/dist/components/FieldConfigDialog.d.ts.map +1 -0
  23. package/dist/components/FieldPalette.d.ts +10 -0
  24. package/dist/components/FieldPalette.d.ts.map +1 -0
  25. package/dist/components/FormBuilder.d.ts +14 -0
  26. package/dist/components/FormBuilder.d.ts.map +1 -0
  27. package/dist/components/FormRenderer.d.ts +11 -0
  28. package/dist/components/FormRenderer.d.ts.map +1 -0
  29. package/dist/components/FormViewer.d.ts +12 -0
  30. package/dist/components/FormViewer.d.ts.map +1 -0
  31. package/dist/components/PersonDocumentsWidget.d.ts +17 -0
  32. package/dist/components/PersonDocumentsWidget.d.ts.map +1 -0
  33. package/dist/components/TemplateSelector.d.ts +11 -0
  34. package/dist/components/TemplateSelector.d.ts.map +1 -0
  35. package/dist/config.d.ts +23 -0
  36. package/dist/config.d.ts.map +1 -0
  37. package/dist/data/index.d.ts +4 -0
  38. package/dist/data/index.d.ts.map +1 -0
  39. package/dist/data/mock.d.ts +3 -0
  40. package/dist/data/mock.d.ts.map +1 -0
  41. package/dist/data/supabase.d.ts +3 -0
  42. package/dist/data/supabase.d.ts.map +1 -0
  43. package/dist/data/types.d.ts +17 -0
  44. package/dist/data/types.d.ts.map +1 -0
  45. package/dist/document-types.d.ts +25 -0
  46. package/dist/document-types.d.ts.map +1 -0
  47. package/dist/index.cjs +1612 -0
  48. package/dist/index.cjs.map +1 -0
  49. package/dist/index.d.ts +6 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +1605 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/lib/tenant.d.ts +3 -0
  54. package/dist/lib/tenant.d.ts.map +1 -0
  55. package/dist/locales/en.d.ts +2 -0
  56. package/dist/locales/en.d.ts.map +1 -0
  57. package/dist/locales/index.d.ts +2 -0
  58. package/dist/locales/index.d.ts.map +1 -0
  59. package/dist/locales/pt-BR.d.ts +2 -0
  60. package/dist/locales/pt-BR.d.ts.map +1 -0
  61. package/dist/store.d.ts +29 -0
  62. package/dist/store.d.ts.map +1 -0
  63. package/dist/types.d.ts +150 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/views/CustomFormsSettingsTab.d.ts +14 -0
  66. package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -0
  67. package/dist/views/TemplateListView.d.ts +13 -0
  68. package/dist/views/TemplateListView.d.ts.map +1 -0
  69. package/package.json +57 -0
  70. package/src/CustomFormsContext.tsx +28 -0
  71. package/src/components/AddDocumentDropdown.tsx +92 -0
  72. package/src/components/BuilderCanvas.tsx +87 -0
  73. package/src/components/BuilderField.tsx +81 -0
  74. package/src/components/DocumentFormDialog.tsx +134 -0
  75. package/src/components/DocumentList.tsx +250 -0
  76. package/src/components/FieldConfigDialog.tsx +203 -0
  77. package/src/components/FieldPalette.tsx +81 -0
  78. package/src/components/FormBuilder.tsx +345 -0
  79. package/src/components/FormRenderer.tsx +256 -0
  80. package/src/components/FormViewer.tsx +84 -0
  81. package/src/components/PersonDocumentsWidget.tsx +220 -0
  82. package/src/components/TemplateSelector.tsx +91 -0
  83. package/src/config.ts +39 -0
  84. package/src/data/index.ts +3 -0
  85. package/src/data/mock.ts +193 -0
  86. package/src/data/supabase.ts +405 -0
  87. package/src/data/types.ts +33 -0
  88. package/src/document-types.ts +51 -0
  89. package/src/index.ts +195 -0
  90. package/src/lib/tenant.ts +5 -0
  91. package/src/locales/en.ts +75 -0
  92. package/src/locales/index.ts +7 -0
  93. package/src/locales/pt-BR.ts +75 -0
  94. package/src/migrations/001_frm_base.sql +138 -0
  95. package/src/migrations/002_document_archetype.sql +135 -0
  96. package/src/store.ts +167 -0
  97. package/src/types.ts +203 -0
  98. package/src/views/CustomFormsSettingsTab.tsx +115 -0
  99. package/src/views/TemplateListView.tsx +163 -0
@@ -0,0 +1,75 @@
1
+ export const en: Record<string, string> = {
2
+ // Settings
3
+ 'customForms.settingsTitle': 'Forms & Documents',
4
+ 'customForms.settingsSubtitle': 'Create and manage custom forms for your business',
5
+
6
+ // Templates
7
+ 'customForms.templates': 'Templates',
8
+ 'customForms.newTemplate': 'New Template',
9
+ 'customForms.editTemplate': 'Edit Template',
10
+ 'customForms.templateName': 'Form name',
11
+ 'customForms.templateDescription': 'Description',
12
+ 'customForms.templateCategory': 'Category',
13
+ 'customForms.templateSpecialty': 'Specialty',
14
+ 'customForms.noTemplates': 'No form templates yet',
15
+ 'customForms.noTemplatesDescription': 'Create your first custom form to start collecting structured data.',
16
+ 'customForms.deleteTemplateConfirm': 'Delete this form template? Existing documents will be preserved.',
17
+
18
+ // Categories
19
+ 'customForms.category.anamnesis': 'Anamnesis',
20
+ 'customForms.category.evolution': 'Evolution Note',
21
+ 'customForms.category.report': 'Report',
22
+ 'customForms.category.contract': 'Contract',
23
+ 'customForms.category.general': 'General',
24
+
25
+ // Builder
26
+ 'customForms.builder.fields': 'Fields',
27
+ 'customForms.builder.fieldCount': '{{count}} fields',
28
+ 'customForms.builder.settings': 'Settings',
29
+ 'customForms.builder.save': 'Save',
30
+ 'customForms.builder.saving': 'Saving...',
31
+ 'customForms.builder.saved': 'Form saved',
32
+ 'customForms.builder.emptyCanvas': 'Drag fields from the left panel to start building your form',
33
+ 'customForms.builder.fieldLabel': 'Field label',
34
+ 'customForms.builder.fieldPlaceholder': 'Placeholder',
35
+ 'customForms.builder.fieldRequired': 'Required',
36
+ 'customForms.builder.fieldOptions': 'Options',
37
+ 'customForms.builder.addOption': 'Add option',
38
+ 'customForms.builder.colSpan': 'Column span',
39
+ 'customForms.builder.deleteField': 'Remove field',
40
+
41
+ // Field types
42
+ 'customForms.fieldType.title': 'Title',
43
+ 'customForms.fieldType.text': 'Simple Text',
44
+ 'customForms.fieldType.memo': 'Memo',
45
+ 'customForms.fieldType.richtext': 'Rich Text',
46
+ 'customForms.fieldType.date': 'Date',
47
+ 'customForms.fieldType.select': 'Select',
48
+ 'customForms.fieldType.radio': 'Radio',
49
+ 'customForms.fieldType.tags': 'Tags',
50
+ 'customForms.fieldType.checkbox': 'Checkbox',
51
+ 'customForms.fieldType.image': 'Image / Drawing',
52
+ 'customForms.fieldType.gallery': 'Gallery',
53
+ 'customForms.fieldType.budget': 'Budget',
54
+
55
+ // Documents
56
+ 'customForms.documents': 'Documents',
57
+ 'customForms.addDocument': 'Add Document',
58
+ 'customForms.selectTemplate': 'Select a form',
59
+ 'customForms.noDocuments': 'No documents yet',
60
+ 'customForms.noDocumentsDescription': 'Add a document to start recording information.',
61
+ 'customForms.documentTitle': 'Document title',
62
+ 'customForms.saveAsDraft': 'Save as Draft',
63
+ 'customForms.saveAndComplete': 'Save & Complete',
64
+ 'customForms.deleteDocumentConfirm': 'Delete this document?',
65
+
66
+ // Registry
67
+ 'registry.form-categories': 'Categories',
68
+ 'registry.form-categories.description': 'Categories for organizing form templates',
69
+
70
+ // Status
71
+ 'customForms.status.draft': 'Draft',
72
+ 'customForms.status.completed': 'Completed',
73
+ 'customForms.status.signed': 'Signed',
74
+ 'customForms.status.archived': 'Archived',
75
+ }
@@ -0,0 +1,7 @@
1
+ import { en } from './en'
2
+ import { ptBR } from './pt-BR'
3
+
4
+ export const customFormsLocales: Record<string, Record<string, string>> = {
5
+ en,
6
+ 'pt-BR': ptBR,
7
+ }
@@ -0,0 +1,75 @@
1
+ export const ptBR: Record<string, string> = {
2
+ // Settings
3
+ 'customForms.settingsTitle': 'Formulários e Documentos',
4
+ 'customForms.settingsSubtitle': 'Crie e gerencie formulários personalizados para o seu negócio',
5
+
6
+ // Templates
7
+ 'customForms.templates': 'Modelos',
8
+ 'customForms.newTemplate': 'Novo Modelo',
9
+ 'customForms.editTemplate': 'Editar Modelo',
10
+ 'customForms.templateName': 'Nome do formulário',
11
+ 'customForms.templateDescription': 'Descrição',
12
+ 'customForms.templateCategory': 'Categoria',
13
+ 'customForms.templateSpecialty': 'Especialidade',
14
+ 'customForms.noTemplates': 'Nenhum modelo criado',
15
+ 'customForms.noTemplatesDescription': 'Crie seu primeiro formulário personalizado para começar a coletar dados estruturados.',
16
+ 'customForms.deleteTemplateConfirm': 'Excluir este modelo de formulário? Documentos existentes serão preservados.',
17
+
18
+ // Categories
19
+ 'customForms.category.anamnesis': 'Anamnese',
20
+ 'customForms.category.evolution': 'Evolução',
21
+ 'customForms.category.report': 'Laudo',
22
+ 'customForms.category.contract': 'Contrato',
23
+ 'customForms.category.general': 'Geral',
24
+
25
+ // Builder
26
+ 'customForms.builder.fields': 'Campos',
27
+ 'customForms.builder.fieldCount': '{{count}} campos',
28
+ 'customForms.builder.settings': 'Configurações',
29
+ 'customForms.builder.save': 'Salvar',
30
+ 'customForms.builder.saving': 'Salvando...',
31
+ 'customForms.builder.saved': 'Formulário salvo',
32
+ 'customForms.builder.emptyCanvas': 'Arraste os campos do painel esquerdo para começar a montar seu formulário',
33
+ 'customForms.builder.fieldLabel': 'Rótulo do campo',
34
+ 'customForms.builder.fieldPlaceholder': 'Placeholder',
35
+ 'customForms.builder.fieldRequired': 'Obrigatório',
36
+ 'customForms.builder.fieldOptions': 'Opções',
37
+ 'customForms.builder.addOption': 'Adicionar opção',
38
+ 'customForms.builder.colSpan': 'Largura em colunas',
39
+ 'customForms.builder.deleteField': 'Remover campo',
40
+
41
+ // Field types
42
+ 'customForms.fieldType.title': 'Título',
43
+ 'customForms.fieldType.text': 'Texto Simples',
44
+ 'customForms.fieldType.memo': 'Memorando',
45
+ 'customForms.fieldType.richtext': 'Texto Rico',
46
+ 'customForms.fieldType.date': 'Data',
47
+ 'customForms.fieldType.select': 'Seleção',
48
+ 'customForms.fieldType.radio': 'Radio',
49
+ 'customForms.fieldType.tags': 'Tags',
50
+ 'customForms.fieldType.checkbox': 'Checkbox',
51
+ 'customForms.fieldType.image': 'Imagem / Desenho',
52
+ 'customForms.fieldType.gallery': 'Galeria',
53
+ 'customForms.fieldType.budget': 'Orçamento',
54
+
55
+ // Documents
56
+ 'customForms.documents': 'Documentos',
57
+ 'customForms.addDocument': 'Novo Documento',
58
+ 'customForms.selectTemplate': 'Selecione um formulário',
59
+ 'customForms.noDocuments': 'Nenhum documento',
60
+ 'customForms.noDocumentsDescription': 'Adicione um documento para começar a registrar informações.',
61
+ 'customForms.documentTitle': 'Título do documento',
62
+ 'customForms.saveAsDraft': 'Salvar Rascunho',
63
+ 'customForms.saveAndComplete': 'Salvar e Finalizar',
64
+ 'customForms.deleteDocumentConfirm': 'Excluir este documento?',
65
+
66
+ // Registry
67
+ 'registry.form-categories': 'Categorias',
68
+ 'registry.form-categories.description': 'Categorias para organizar modelos de formulários',
69
+
70
+ // Status
71
+ 'customForms.status.draft': 'Rascunho',
72
+ 'customForms.status.completed': 'Finalizado',
73
+ 'customForms.status.signed': 'Assinado',
74
+ 'customForms.status.archived': 'Arquivado',
75
+ }
@@ -0,0 +1,138 @@
1
+ -- ============================================================
2
+ -- Custom Forms Plugin — Base Tables
3
+ -- ============================================================
4
+
5
+ -- frm_templates: form template definitions (versioned)
6
+ CREATE TABLE IF NOT EXISTS public.frm_templates (
7
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
8
+ tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
9
+ name text NOT NULL,
10
+ description text,
11
+ category text NOT NULL DEFAULT 'general',
12
+ version integer NOT NULL DEFAULT 1,
13
+ is_current boolean NOT NULL DEFAULT true,
14
+ parent_id uuid REFERENCES public.frm_templates(id),
15
+ schema jsonb NOT NULL DEFAULT '{"fields":[],"layout":{"columns":12}}',
16
+ specialty text,
17
+ tags text[] DEFAULT '{}',
18
+ metadata jsonb DEFAULT '{}',
19
+ is_active boolean NOT NULL DEFAULT true,
20
+ is_deleted boolean NOT NULL DEFAULT false,
21
+ created_by uuid,
22
+ updated_by uuid,
23
+ created_at timestamptz NOT NULL DEFAULT now(),
24
+ updated_at timestamptz NOT NULL DEFAULT now()
25
+ );
26
+
27
+ ALTER TABLE public.frm_templates ENABLE ROW LEVEL SECURITY;
28
+
29
+ CREATE INDEX IF NOT EXISTS idx_frm_templates_tenant
30
+ ON public.frm_templates(tenant_id);
31
+ CREATE INDEX IF NOT EXISTS idx_frm_templates_parent
32
+ ON public.frm_templates(parent_id);
33
+ CREATE INDEX IF NOT EXISTS idx_frm_templates_category
34
+ ON public.frm_templates(tenant_id, category);
35
+ CREATE INDEX IF NOT EXISTS idx_frm_templates_current
36
+ ON public.frm_templates(tenant_id, is_current, is_active)
37
+ WHERE is_current = true AND is_active = true AND is_deleted = false;
38
+
39
+ CREATE POLICY "frm_templates_select" ON public.frm_templates
40
+ FOR SELECT TO authenticated
41
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
42
+ CREATE POLICY "frm_templates_insert" ON public.frm_templates
43
+ FOR INSERT TO authenticated
44
+ WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
45
+ CREATE POLICY "frm_templates_update" ON public.frm_templates
46
+ FOR UPDATE TO authenticated
47
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
48
+ CREATE POLICY "frm_templates_delete" ON public.frm_templates
49
+ FOR DELETE TO authenticated
50
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
51
+
52
+ -- frm_documents: filled form instances
53
+ CREATE TABLE IF NOT EXISTS public.frm_documents (
54
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
55
+ tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
56
+ template_id uuid NOT NULL REFERENCES public.frm_templates(id),
57
+ person_id uuid REFERENCES saas_core.persons(id) ON DELETE SET NULL,
58
+ title text,
59
+ data jsonb NOT NULL DEFAULT '{}',
60
+ status text NOT NULL DEFAULT 'draft',
61
+ signed_at timestamptz,
62
+ signed_by uuid,
63
+ notes text,
64
+ metadata jsonb DEFAULT '{}',
65
+ is_deleted boolean NOT NULL DEFAULT false,
66
+ created_by uuid,
67
+ updated_by uuid,
68
+ created_at timestamptz NOT NULL DEFAULT now(),
69
+ updated_at timestamptz NOT NULL DEFAULT now()
70
+ );
71
+
72
+ ALTER TABLE public.frm_documents ENABLE ROW LEVEL SECURITY;
73
+
74
+ CREATE INDEX IF NOT EXISTS idx_frm_documents_tenant
75
+ ON public.frm_documents(tenant_id);
76
+ CREATE INDEX IF NOT EXISTS idx_frm_documents_person
77
+ ON public.frm_documents(person_id);
78
+ CREATE INDEX IF NOT EXISTS idx_frm_documents_template
79
+ ON public.frm_documents(template_id);
80
+ CREATE INDEX IF NOT EXISTS idx_frm_documents_status
81
+ ON public.frm_documents(tenant_id, status);
82
+
83
+ CREATE POLICY "frm_documents_select" ON public.frm_documents
84
+ FOR SELECT TO authenticated
85
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
86
+ CREATE POLICY "frm_documents_insert" ON public.frm_documents
87
+ FOR INSERT TO authenticated
88
+ WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
89
+ CREATE POLICY "frm_documents_update" ON public.frm_documents
90
+ FOR UPDATE TO authenticated
91
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
92
+ CREATE POLICY "frm_documents_delete" ON public.frm_documents
93
+ FOR DELETE TO authenticated
94
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
95
+
96
+ -- frm_document_files: file attachments for image/gallery/drawing fields
97
+ CREATE TABLE IF NOT EXISTS public.frm_document_files (
98
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
99
+ tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
100
+ document_id uuid NOT NULL REFERENCES public.frm_documents(id) ON DELETE CASCADE,
101
+ field_key text NOT NULL,
102
+ file_url text NOT NULL,
103
+ file_name text,
104
+ file_size integer,
105
+ mime_type text,
106
+ sort_order integer DEFAULT 0,
107
+ metadata jsonb DEFAULT '{}',
108
+ created_at timestamptz NOT NULL DEFAULT now()
109
+ );
110
+
111
+ ALTER TABLE public.frm_document_files ENABLE ROW LEVEL SECURITY;
112
+
113
+ CREATE INDEX IF NOT EXISTS idx_frm_document_files_document
114
+ ON public.frm_document_files(document_id);
115
+
116
+ CREATE POLICY "frm_document_files_select" ON public.frm_document_files
117
+ FOR SELECT TO authenticated
118
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
119
+ CREATE POLICY "frm_document_files_insert" ON public.frm_document_files
120
+ FOR INSERT TO authenticated
121
+ WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
122
+ CREATE POLICY "frm_document_files_update" ON public.frm_document_files
123
+ FOR UPDATE TO authenticated
124
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
125
+ CREATE POLICY "frm_document_files_delete" ON public.frm_document_files
126
+ FOR DELETE TO authenticated
127
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
128
+
129
+ -- View: frm_documents with template name joined
130
+ CREATE OR REPLACE VIEW public.v_frm_documents AS
131
+ SELECT
132
+ d.*,
133
+ t.name AS template_name,
134
+ t.category AS template_category,
135
+ p.name AS person_name
136
+ FROM public.frm_documents d
137
+ LEFT JOIN public.frm_templates t ON t.id = d.template_id
138
+ LEFT JOIN saas_core.persons p ON p.id = d.person_id;
@@ -0,0 +1,135 @@
1
+ -- ============================================================
2
+ -- Document Archetype — saas_core.documents
3
+ -- A document is any record associated with a person (or standalone):
4
+ -- forms, images, attachments, prescriptions, contracts, etc.
5
+ -- The `kind` column discriminates the type.
6
+ -- ============================================================
7
+
8
+ CREATE TABLE IF NOT EXISTS saas_core.documents (
9
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
10
+ tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
11
+ kind text NOT NULL DEFAULT 'attachment',
12
+ -- kind: 'form', 'image', 'attachment', 'prescription', 'contract', etc.
13
+ person_id uuid REFERENCES saas_core.persons(id) ON DELETE SET NULL,
14
+ title text,
15
+ description text,
16
+ status text NOT NULL DEFAULT 'draft',
17
+ -- status: 'draft', 'completed', 'signed', 'archived'
18
+ file_url text,
19
+ file_name text,
20
+ file_size integer,
21
+ mime_type text,
22
+ tags text[] DEFAULT '{}',
23
+ notes text,
24
+ is_active boolean NOT NULL DEFAULT true,
25
+ metadata jsonb DEFAULT '{}',
26
+ created_by uuid,
27
+ updated_by uuid,
28
+ created_at timestamptz NOT NULL DEFAULT now(),
29
+ updated_at timestamptz NOT NULL DEFAULT now()
30
+ );
31
+
32
+ ALTER TABLE saas_core.documents ENABLE ROW LEVEL SECURITY;
33
+
34
+ CREATE INDEX IF NOT EXISTS idx_documents_tenant ON saas_core.documents(tenant_id, kind);
35
+ CREATE INDEX IF NOT EXISTS idx_documents_person ON saas_core.documents(person_id);
36
+ CREATE INDEX IF NOT EXISTS idx_documents_status ON saas_core.documents(tenant_id, status);
37
+
38
+ CREATE POLICY "documents_select" ON saas_core.documents
39
+ FOR SELECT TO authenticated
40
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
41
+ CREATE POLICY "documents_insert" ON saas_core.documents
42
+ FOR INSERT TO authenticated
43
+ WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
44
+ CREATE POLICY "documents_update" ON saas_core.documents
45
+ FOR UPDATE TO authenticated
46
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
47
+ CREATE POLICY "documents_delete" ON saas_core.documents
48
+ FOR DELETE TO authenticated
49
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
50
+
51
+ -- ============================================================
52
+ -- Alter frm_documents to become extension table for 'form' kind
53
+ -- ============================================================
54
+
55
+ -- Drop old frm_documents and recreate as extension
56
+ DROP VIEW IF EXISTS public.v_frm_documents;
57
+ DROP TABLE IF EXISTS public.frm_document_files;
58
+ DROP TABLE IF EXISTS public.frm_documents;
59
+
60
+ -- frm_documents: extension table for form-type documents
61
+ CREATE TABLE public.frm_documents (
62
+ document_id uuid PRIMARY KEY REFERENCES saas_core.documents(id) ON DELETE CASCADE,
63
+ tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
64
+ template_id uuid NOT NULL REFERENCES public.frm_templates(id),
65
+ data jsonb NOT NULL DEFAULT '{}',
66
+ signed_at timestamptz,
67
+ signed_by uuid,
68
+ created_at timestamptz NOT NULL DEFAULT now(),
69
+ updated_at timestamptz NOT NULL DEFAULT now()
70
+ );
71
+
72
+ ALTER TABLE public.frm_documents ENABLE ROW LEVEL SECURITY;
73
+
74
+ CREATE INDEX IF NOT EXISTS idx_frm_documents_template ON public.frm_documents(template_id);
75
+
76
+ CREATE POLICY "frm_documents_select" ON public.frm_documents
77
+ FOR SELECT TO authenticated
78
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
79
+ CREATE POLICY "frm_documents_insert" ON public.frm_documents
80
+ FOR INSERT TO authenticated
81
+ WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
82
+ CREATE POLICY "frm_documents_update" ON public.frm_documents
83
+ FOR UPDATE TO authenticated
84
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
85
+ CREATE POLICY "frm_documents_delete" ON public.frm_documents
86
+ FOR DELETE TO authenticated
87
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
88
+
89
+ -- frm_document_files: file attachments for form fields
90
+ CREATE TABLE public.frm_document_files (
91
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
92
+ tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
93
+ document_id uuid NOT NULL REFERENCES saas_core.documents(id) ON DELETE CASCADE,
94
+ field_key text NOT NULL,
95
+ file_url text NOT NULL,
96
+ file_name text,
97
+ file_size integer,
98
+ mime_type text,
99
+ sort_order integer DEFAULT 0,
100
+ metadata jsonb DEFAULT '{}',
101
+ created_at timestamptz NOT NULL DEFAULT now()
102
+ );
103
+
104
+ ALTER TABLE public.frm_document_files ENABLE ROW LEVEL SECURITY;
105
+
106
+ CREATE INDEX IF NOT EXISTS idx_frm_document_files_document ON public.frm_document_files(document_id);
107
+
108
+ CREATE POLICY "frm_document_files_select" ON public.frm_document_files
109
+ FOR SELECT TO authenticated
110
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
111
+ CREATE POLICY "frm_document_files_insert" ON public.frm_document_files
112
+ FOR INSERT TO authenticated
113
+ WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
114
+ CREATE POLICY "frm_document_files_update" ON public.frm_document_files
115
+ FOR UPDATE TO authenticated
116
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
117
+ CREATE POLICY "frm_document_files_delete" ON public.frm_document_files
118
+ FOR DELETE TO authenticated
119
+ USING (tenant_id IN (SELECT public.user_tenant_ids()));
120
+
121
+ -- View: all documents for a person with form data joined when applicable
122
+ CREATE OR REPLACE VIEW public.v_documents AS
123
+ SELECT
124
+ d.*,
125
+ f.template_id,
126
+ f.data AS form_data,
127
+ f.signed_at,
128
+ f.signed_by,
129
+ t.name AS template_name,
130
+ t.category AS template_category,
131
+ p.name AS person_name
132
+ FROM saas_core.documents d
133
+ LEFT JOIN public.frm_documents f ON f.document_id = d.id
134
+ LEFT JOIN public.frm_templates t ON t.id = f.template_id
135
+ LEFT JOIN saas_core.persons p ON p.id = d.person_id;
package/src/store.ts ADDED
@@ -0,0 +1,167 @@
1
+ import { create } from 'zustand'
2
+ import type { CustomFormsDataProvider } from './data/types'
3
+ import type {
4
+ FormTemplate,
5
+ FormDocument,
6
+ TemplateQuery,
7
+ DocumentQuery,
8
+ CreateTemplateInput,
9
+ UpdateTemplateInput,
10
+ CreateDocumentInput,
11
+ UpdateDocumentInput,
12
+ } from './types'
13
+
14
+ export interface CustomFormsState {
15
+ // Templates
16
+ templates: FormTemplate[]
17
+ templatesTotal: number
18
+ templatesLoading: boolean
19
+ templatesError: string | null
20
+
21
+ // Documents
22
+ documents: FormDocument[]
23
+ documentsTotal: number
24
+ documentsLoading: boolean
25
+ documentsError: string | null
26
+
27
+ // Active template (for builder)
28
+ activeTemplate: FormTemplate | null
29
+ activeTemplateLoading: boolean
30
+
31
+ // Active document (for viewer/editor)
32
+ activeDocument: FormDocument | null
33
+ activeDocumentLoading: boolean
34
+
35
+ // Actions
36
+ fetchTemplates(query?: TemplateQuery): Promise<void>
37
+ fetchTemplateById(id: string): Promise<FormTemplate | null>
38
+ createTemplate(input: CreateTemplateInput): Promise<FormTemplate>
39
+ updateTemplate(id: string, input: UpdateTemplateInput): Promise<FormTemplate>
40
+ deleteTemplate(id: string): Promise<void>
41
+
42
+ fetchDocuments(query?: DocumentQuery): Promise<void>
43
+ fetchDocumentById(id: string): Promise<FormDocument | null>
44
+ createDocument(input: CreateDocumentInput): Promise<FormDocument>
45
+ updateDocument(id: string, input: UpdateDocumentInput): Promise<FormDocument>
46
+ deleteDocument(id: string): Promise<void>
47
+ }
48
+
49
+ export function createCustomFormsStore(provider: CustomFormsDataProvider) {
50
+ return create<CustomFormsState>((set) => ({
51
+ templates: [],
52
+ templatesTotal: 0,
53
+ templatesLoading: false,
54
+ templatesError: null,
55
+
56
+ documents: [],
57
+ documentsTotal: 0,
58
+ documentsLoading: false,
59
+ documentsError: null,
60
+
61
+ activeTemplate: null,
62
+ activeTemplateLoading: false,
63
+
64
+ activeDocument: null,
65
+ activeDocumentLoading: false,
66
+
67
+ async fetchTemplates(query?: TemplateQuery) {
68
+ set({ templatesLoading: true, templatesError: null })
69
+ try {
70
+ const result = await provider.getTemplates(query ?? {})
71
+ set({ templates: result.data, templatesTotal: result.total })
72
+ } catch (e: any) {
73
+ set({ templatesError: e.message ?? 'Failed to fetch templates' })
74
+ } finally {
75
+ set({ templatesLoading: false })
76
+ }
77
+ },
78
+
79
+ async fetchTemplateById(id: string) {
80
+ set({ activeTemplateLoading: true })
81
+ try {
82
+ const template = await provider.getTemplateById(id)
83
+ set({ activeTemplate: template })
84
+ return template
85
+ } catch {
86
+ set({ activeTemplate: null })
87
+ return null
88
+ } finally {
89
+ set({ activeTemplateLoading: false })
90
+ }
91
+ },
92
+
93
+ async createTemplate(input: CreateTemplateInput) {
94
+ const template = await provider.createTemplate(input)
95
+ set((s) => ({ templates: [template, ...s.templates], templatesTotal: s.templatesTotal + 1 }))
96
+ return template
97
+ },
98
+
99
+ async updateTemplate(id: string, input: UpdateTemplateInput) {
100
+ const template = await provider.updateTemplate(id, input)
101
+ set((s) => ({
102
+ templates: s.templates.map((t) => (t.id === id ? template : t)),
103
+ activeTemplate: s.activeTemplate?.id === id ? template : s.activeTemplate,
104
+ }))
105
+ return template
106
+ },
107
+
108
+ async deleteTemplate(id: string) {
109
+ await provider.deleteTemplate(id)
110
+ set((s) => ({
111
+ templates: s.templates.filter((t) => t.id !== id),
112
+ templatesTotal: s.templatesTotal - 1,
113
+ }))
114
+ },
115
+
116
+ async fetchDocuments(query?: DocumentQuery) {
117
+ set({ documentsLoading: true, documentsError: null })
118
+ try {
119
+ const result = await provider.getDocuments(query ?? {})
120
+ set({ documents: result.data, documentsTotal: result.total })
121
+ } catch (e: any) {
122
+ set({ documentsError: e.message ?? 'Failed to fetch documents' })
123
+ } finally {
124
+ set({ documentsLoading: false })
125
+ }
126
+ },
127
+
128
+ async fetchDocumentById(id: string) {
129
+ set({ activeDocumentLoading: true })
130
+ try {
131
+ const doc = await provider.getDocumentById(id)
132
+ set({ activeDocument: doc })
133
+ return doc
134
+ } catch {
135
+ set({ activeDocument: null })
136
+ return null
137
+ } finally {
138
+ set({ activeDocumentLoading: false })
139
+ }
140
+ },
141
+
142
+ async createDocument(input: CreateDocumentInput) {
143
+ const doc = await provider.createDocument(input)
144
+ set((s) => ({ documents: [doc, ...s.documents], documentsTotal: s.documentsTotal + 1 }))
145
+ return doc
146
+ },
147
+
148
+ async updateDocument(id: string, input: UpdateDocumentInput) {
149
+ const doc = await provider.updateDocument(id, input)
150
+ set((s) => ({
151
+ documents: s.documents.map((d) => (d.id === id ? doc : d)),
152
+ activeDocument: s.activeDocument?.id === id ? doc : s.activeDocument,
153
+ }))
154
+ return doc
155
+ },
156
+
157
+ async deleteDocument(id: string) {
158
+ await provider.deleteDocument(id)
159
+ set((s) => ({
160
+ documents: s.documents.filter((d) => d.id !== id),
161
+ documentsTotal: s.documentsTotal - 1,
162
+ }))
163
+ },
164
+ }))
165
+ }
166
+
167
+ export type CustomFormsStore = ReturnType<typeof createCustomFormsStore>