@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.
- package/dist/CustomFormsContext.d.ts +15 -0
- package/dist/CustomFormsContext.d.ts.map +1 -0
- package/dist/FormBuilder-4VKYVZAC.cjs +619 -0
- package/dist/FormBuilder-4VKYVZAC.cjs.map +1 -0
- package/dist/FormBuilder-KDANEL6Z.js +613 -0
- package/dist/FormBuilder-KDANEL6Z.js.map +1 -0
- package/dist/chunk-32I43T37.js +198 -0
- package/dist/chunk-32I43T37.js.map +1 -0
- package/dist/chunk-XLUULIVL.cjs +200 -0
- package/dist/chunk-XLUULIVL.cjs.map +1 -0
- package/dist/components/AddDocumentDropdown.d.ts +9 -0
- package/dist/components/AddDocumentDropdown.d.ts.map +1 -0
- package/dist/components/BuilderCanvas.d.ts +12 -0
- package/dist/components/BuilderCanvas.d.ts.map +1 -0
- package/dist/components/BuilderField.d.ts +11 -0
- package/dist/components/BuilderField.d.ts.map +1 -0
- package/dist/components/DocumentFormDialog.d.ts +17 -0
- package/dist/components/DocumentFormDialog.d.ts.map +1 -0
- package/dist/components/DocumentList.d.ts +14 -0
- package/dist/components/DocumentList.d.ts.map +1 -0
- package/dist/components/FieldConfigDialog.d.ts +12 -0
- package/dist/components/FieldConfigDialog.d.ts.map +1 -0
- package/dist/components/FieldPalette.d.ts +10 -0
- package/dist/components/FieldPalette.d.ts.map +1 -0
- package/dist/components/FormBuilder.d.ts +14 -0
- package/dist/components/FormBuilder.d.ts.map +1 -0
- package/dist/components/FormRenderer.d.ts +11 -0
- package/dist/components/FormRenderer.d.ts.map +1 -0
- package/dist/components/FormViewer.d.ts +12 -0
- package/dist/components/FormViewer.d.ts.map +1 -0
- package/dist/components/PersonDocumentsWidget.d.ts +17 -0
- package/dist/components/PersonDocumentsWidget.d.ts.map +1 -0
- package/dist/components/TemplateSelector.d.ts +11 -0
- package/dist/components/TemplateSelector.d.ts.map +1 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/data/index.d.ts +4 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/mock.d.ts +3 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/types.d.ts +17 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/document-types.d.ts +25 -0
- package/dist/document-types.d.ts.map +1 -0
- package/dist/index.cjs +1612 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1605 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tenant.d.ts +3 -0
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/store.d.ts +29 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/CustomFormsSettingsTab.d.ts +14 -0
- package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -0
- package/dist/views/TemplateListView.d.ts +13 -0
- package/dist/views/TemplateListView.d.ts.map +1 -0
- package/package.json +57 -0
- package/src/CustomFormsContext.tsx +28 -0
- package/src/components/AddDocumentDropdown.tsx +92 -0
- package/src/components/BuilderCanvas.tsx +87 -0
- package/src/components/BuilderField.tsx +81 -0
- package/src/components/DocumentFormDialog.tsx +134 -0
- package/src/components/DocumentList.tsx +250 -0
- package/src/components/FieldConfigDialog.tsx +203 -0
- package/src/components/FieldPalette.tsx +81 -0
- package/src/components/FormBuilder.tsx +345 -0
- package/src/components/FormRenderer.tsx +256 -0
- package/src/components/FormViewer.tsx +84 -0
- package/src/components/PersonDocumentsWidget.tsx +220 -0
- package/src/components/TemplateSelector.tsx +91 -0
- package/src/config.ts +39 -0
- package/src/data/index.ts +3 -0
- package/src/data/mock.ts +193 -0
- package/src/data/supabase.ts +405 -0
- package/src/data/types.ts +33 -0
- package/src/document-types.ts +51 -0
- package/src/index.ts +195 -0
- package/src/lib/tenant.ts +5 -0
- package/src/locales/en.ts +75 -0
- package/src/locales/index.ts +7 -0
- package/src/locales/pt-BR.ts +75 -0
- package/src/migrations/001_frm_base.sql +138 -0
- package/src/migrations/002_document_archetype.sql +135 -0
- package/src/store.ts +167 -0
- package/src/types.ts +203 -0
- package/src/views/CustomFormsSettingsTab.tsx +115 -0
- 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,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>
|