@fayz-ai/plugin-forms 0.9.0-next.1 → 0.9.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/README.md +3 -3
- package/dist/{FormBuilder-7SWZQKFN.js → FormBuilder-JCNHXQXT.js} +4 -4
- package/dist/{FormBuilder-7SWZQKFN.js.map → FormBuilder-JCNHXQXT.js.map} +1 -1
- package/dist/{chunk-W2ZNJGQC.js → chunk-JCAWSDX6.js} +3 -3
- package/dist/chunk-JCAWSDX6.js.map +1 -0
- package/dist/components/AddDocumentDropdown.d.ts +1 -1
- package/dist/components/AddDocumentDropdown.d.ts.map +1 -1
- package/dist/{CustomFormsContext.d.ts → context.d.ts} +1 -1
- package/dist/context.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-tools.d.ts.map +1 -0
- package/dist/lib/document-types.d.ts.map +1 -0
- package/package.json +5 -7
- package/dist/CustomFormsContext.d.ts.map +0 -1
- package/dist/agent-tools.d.ts.map +0 -1
- package/dist/chunk-W2ZNJGQC.js.map +0 -1
- package/dist/document-types.d.ts.map +0 -1
- package/src/CustomFormsContext.tsx +0 -28
- package/src/agent-tools.ts +0 -73
- package/src/components/AddDocumentDropdown.tsx +0 -92
- package/src/components/BuilderCanvas.tsx +0 -87
- package/src/components/BuilderField.tsx +0 -81
- package/src/components/DocumentFormDialog.tsx +0 -155
- package/src/components/DocumentList.tsx +0 -254
- package/src/components/FieldConfigDialog.tsx +0 -243
- package/src/components/FieldPalette.tsx +0 -81
- package/src/components/FormBuilder.tsx +0 -427
- package/src/components/FormRenderer.tsx +0 -256
- package/src/components/FormViewer.tsx +0 -94
- package/src/components/PersonDocumentsWidget.tsx +0 -229
- package/src/components/TemplateSelector.tsx +0 -91
- package/src/config.ts +0 -43
- package/src/data/index.ts +0 -3
- package/src/data/mock.ts +0 -193
- package/src/data/supabase.ts +0 -408
- package/src/data/tables.ts +0 -7
- package/src/data/types.ts +0 -33
- package/src/document-types.ts +0 -51
- package/src/index.ts +0 -234
- package/src/lib/person-fields.ts +0 -67
- package/src/lib/print.ts +0 -207
- package/src/lib/tenant.ts +0 -9
- package/src/locales/en.ts +0 -95
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -95
- package/src/migrations/000_plg_rename.sql +0 -21
- package/src/migrations/001_frm_base.sql +0 -174
- package/src/migrations/002_document_archetype.sql +0 -223
- package/src/migrations/003_agent_rpcs.sql +0 -174
- package/src/migrations/index.ts +0 -610
- package/src/store.ts +0 -167
- package/src/types.ts +0 -217
- package/src/views/CustomFormsSettingsTab.tsx +0 -105
- package/src/views/TemplateListView.tsx +0 -174
- /package/dist/{agent-tools.d.ts → lib/agent-tools.d.ts} +0 -0
- /package/dist/{document-types.d.ts → lib/document-types.d.ts} +0 -0
package/src/config.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { PluginRegistryDef, PluginScope, VerticalId } from '@fayz-ai/core'
|
|
2
|
-
import type { CustomFormsDataProvider } from './data/types'
|
|
3
|
-
|
|
4
|
-
export interface CustomFormsPluginOptions {
|
|
5
|
-
scope?: PluginScope
|
|
6
|
-
verticalId?: VerticalId
|
|
7
|
-
dataProvider?: CustomFormsDataProvider
|
|
8
|
-
navSection?: 'main' | 'secondary' | 'settings'
|
|
9
|
-
navPosition?: number
|
|
10
|
-
labels?: Partial<CustomFormsLabels>
|
|
11
|
-
/** App-owned registries shown inside Forms & Documents settings. */
|
|
12
|
-
settingsRegistries?: PluginRegistryDef[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CustomFormsLabels {
|
|
16
|
-
pageTitle: string
|
|
17
|
-
settingsLabel: string
|
|
18
|
-
settingsSubtitle: string
|
|
19
|
-
templates: string
|
|
20
|
-
documents: string
|
|
21
|
-
newTemplate: string
|
|
22
|
-
addDocument: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface CustomFormsConfig {
|
|
26
|
-
labels: CustomFormsLabels
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const DEFAULT_LABELS: CustomFormsLabels = {
|
|
30
|
-
pageTitle: 'Custom Forms',
|
|
31
|
-
settingsLabel: 'Forms & Documents',
|
|
32
|
-
settingsSubtitle: 'Create and manage custom forms for your business',
|
|
33
|
-
templates: 'Templates',
|
|
34
|
-
documents: 'Documents',
|
|
35
|
-
newTemplate: 'New Template',
|
|
36
|
-
addDocument: 'Add Document',
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function resolveConfig(options?: CustomFormsPluginOptions): CustomFormsConfig {
|
|
40
|
-
return {
|
|
41
|
-
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
42
|
-
}
|
|
43
|
-
}
|
package/src/data/index.ts
DELETED
package/src/data/mock.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import type { CustomFormsDataProvider } from './types'
|
|
2
|
-
import type {
|
|
3
|
-
FormTemplate,
|
|
4
|
-
FormDocument,
|
|
5
|
-
DocumentFile,
|
|
6
|
-
TemplateQuery,
|
|
7
|
-
DocumentQuery,
|
|
8
|
-
PaginatedResult,
|
|
9
|
-
CreateTemplateInput,
|
|
10
|
-
UpdateTemplateInput,
|
|
11
|
-
CreateDocumentInput,
|
|
12
|
-
UpdateDocumentInput,
|
|
13
|
-
} from '../types'
|
|
14
|
-
|
|
15
|
-
function uuid() {
|
|
16
|
-
return crypto.randomUUID()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function now() {
|
|
20
|
-
return new Date().toISOString()
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function createMockFormsProvider(): CustomFormsDataProvider {
|
|
24
|
-
const templates: FormTemplate[] = []
|
|
25
|
-
const documents: FormDocument[] = []
|
|
26
|
-
const files: DocumentFile[] = []
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
// ── Templates ──────────────────────────────────────────
|
|
30
|
-
async getTemplates(query: TemplateQuery): Promise<PaginatedResult<FormTemplate>> {
|
|
31
|
-
let filtered = templates.filter((t) => !t.isDeleted && t.isCurrent)
|
|
32
|
-
if (query.category) {
|
|
33
|
-
filtered = filtered.filter((t) => t.category === query.category)
|
|
34
|
-
}
|
|
35
|
-
if (query.search) {
|
|
36
|
-
const s = query.search.toLowerCase()
|
|
37
|
-
filtered = filtered.filter((t) => t.name.toLowerCase().includes(s))
|
|
38
|
-
}
|
|
39
|
-
const page = query.page ?? 1
|
|
40
|
-
const pageSize = query.pageSize ?? 25
|
|
41
|
-
const start = (page - 1) * pageSize
|
|
42
|
-
return { data: filtered.slice(start, start + pageSize), total: filtered.length }
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
async getTemplateById(id: string): Promise<FormTemplate | null> {
|
|
46
|
-
return templates.find((t) => t.id === id && !t.isDeleted) ?? null
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
async createTemplate(input: CreateTemplateInput): Promise<FormTemplate> {
|
|
50
|
-
const template: FormTemplate = {
|
|
51
|
-
id: uuid(),
|
|
52
|
-
tenantId: 'mock-tenant',
|
|
53
|
-
name: input.name,
|
|
54
|
-
description: input.description,
|
|
55
|
-
category: input.category,
|
|
56
|
-
version: 1,
|
|
57
|
-
isCurrent: true,
|
|
58
|
-
schema: input.schema,
|
|
59
|
-
specialty: input.specialty,
|
|
60
|
-
tags: input.tags ?? [],
|
|
61
|
-
metadata: input.metadata ?? {},
|
|
62
|
-
isActive: true,
|
|
63
|
-
isDeleted: false,
|
|
64
|
-
createdAt: now(),
|
|
65
|
-
updatedAt: now(),
|
|
66
|
-
}
|
|
67
|
-
templates.push(template)
|
|
68
|
-
return template
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
async updateTemplate(id: string, input: UpdateTemplateInput): Promise<FormTemplate> {
|
|
72
|
-
const idx = templates.findIndex((t) => t.id === id)
|
|
73
|
-
if (idx === -1) throw new Error('Template not found')
|
|
74
|
-
const existing = templates[idx]
|
|
75
|
-
const updated: FormTemplate = {
|
|
76
|
-
...existing,
|
|
77
|
-
...input,
|
|
78
|
-
tags: input.tags ?? existing.tags,
|
|
79
|
-
metadata: input.metadata ?? existing.metadata,
|
|
80
|
-
schema: input.schema ?? existing.schema,
|
|
81
|
-
updatedAt: now(),
|
|
82
|
-
}
|
|
83
|
-
templates[idx] = updated
|
|
84
|
-
return updated
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
async deleteTemplate(id: string): Promise<void> {
|
|
88
|
-
const idx = templates.findIndex((t) => t.id === id)
|
|
89
|
-
if (idx !== -1) templates[idx] = { ...templates[idx], isDeleted: true }
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
// ── Documents ──────────────────────────────────────────
|
|
93
|
-
async getDocuments(query: DocumentQuery): Promise<PaginatedResult<FormDocument>> {
|
|
94
|
-
let filtered = documents.filter((d) => !d.isDeleted)
|
|
95
|
-
if (query.personId) {
|
|
96
|
-
filtered = filtered.filter((d) => d.personId === query.personId)
|
|
97
|
-
}
|
|
98
|
-
if (query.templateId) {
|
|
99
|
-
filtered = filtered.filter((d) => d.templateId === query.templateId)
|
|
100
|
-
}
|
|
101
|
-
if (query.status) {
|
|
102
|
-
const statuses = Array.isArray(query.status) ? query.status : [query.status]
|
|
103
|
-
filtered = filtered.filter((d) => statuses.includes(d.status))
|
|
104
|
-
}
|
|
105
|
-
if (query.search) {
|
|
106
|
-
const s = query.search.toLowerCase()
|
|
107
|
-
filtered = filtered.filter(
|
|
108
|
-
(d) =>
|
|
109
|
-
d.title?.toLowerCase().includes(s) ||
|
|
110
|
-
d.templateName?.toLowerCase().includes(s),
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
filtered.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
114
|
-
const page = query.page ?? 1
|
|
115
|
-
const pageSize = query.pageSize ?? 25
|
|
116
|
-
const start = (page - 1) * pageSize
|
|
117
|
-
return { data: filtered.slice(start, start + pageSize), total: filtered.length }
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
async getDocumentById(id: string): Promise<FormDocument | null> {
|
|
121
|
-
return documents.find((d) => d.id === id && !d.isDeleted) ?? null
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
async createDocument(input: CreateDocumentInput): Promise<FormDocument> {
|
|
125
|
-
const template = templates.find((t) => t.id === input.templateId)
|
|
126
|
-
const doc: FormDocument = {
|
|
127
|
-
id: uuid(),
|
|
128
|
-
tenantId: 'mock-tenant',
|
|
129
|
-
templateId: input.templateId ?? '',
|
|
130
|
-
templateName: template?.name,
|
|
131
|
-
templateCategory: template?.category,
|
|
132
|
-
personId: input.personId,
|
|
133
|
-
title: input.title,
|
|
134
|
-
data: input.data ?? {},
|
|
135
|
-
kind: input.kind ?? (input.templateId ? 'form' : 'attachment'),
|
|
136
|
-
status: input.status ?? 'draft',
|
|
137
|
-
metadata: {},
|
|
138
|
-
isDeleted: false,
|
|
139
|
-
createdAt: now(),
|
|
140
|
-
updatedAt: now(),
|
|
141
|
-
}
|
|
142
|
-
documents.push(doc)
|
|
143
|
-
return doc
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
async updateDocument(id: string, input: UpdateDocumentInput): Promise<FormDocument> {
|
|
147
|
-
const idx = documents.findIndex((d) => d.id === id)
|
|
148
|
-
if (idx === -1) throw new Error('Document not found')
|
|
149
|
-
const existing = documents[idx]
|
|
150
|
-
const updated: FormDocument = {
|
|
151
|
-
...existing,
|
|
152
|
-
...input,
|
|
153
|
-
data: input.data ?? existing.data,
|
|
154
|
-
updatedAt: now(),
|
|
155
|
-
}
|
|
156
|
-
documents[idx] = updated
|
|
157
|
-
return updated
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
async deleteDocument(id: string): Promise<void> {
|
|
161
|
-
const idx = documents.findIndex((d) => d.id === id)
|
|
162
|
-
if (idx !== -1) documents[idx] = { ...documents[idx], isDeleted: true }
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
// ── Files ──────────────────────────────────────────────
|
|
166
|
-
async getDocumentFiles(documentId: string): Promise<DocumentFile[]> {
|
|
167
|
-
return files.filter((f) => f.documentId === documentId)
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
async uploadFile(documentId: string, fieldKey: string, file: File): Promise<DocumentFile> {
|
|
171
|
-
const docFile: DocumentFile = {
|
|
172
|
-
id: uuid(),
|
|
173
|
-
tenantId: 'mock-tenant',
|
|
174
|
-
documentId,
|
|
175
|
-
fieldKey,
|
|
176
|
-
fileUrl: URL.createObjectURL(file),
|
|
177
|
-
fileName: file.name,
|
|
178
|
-
fileSize: file.size,
|
|
179
|
-
mimeType: file.type,
|
|
180
|
-
sortOrder: files.filter((f) => f.documentId === documentId && f.fieldKey === fieldKey).length,
|
|
181
|
-
metadata: {},
|
|
182
|
-
createdAt: now(),
|
|
183
|
-
}
|
|
184
|
-
files.push(docFile)
|
|
185
|
-
return docFile
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
async deleteFile(fileId: string): Promise<void> {
|
|
189
|
-
const idx = files.findIndex((f) => f.id === fileId)
|
|
190
|
-
if (idx !== -1) files.splice(idx, 1)
|
|
191
|
-
},
|
|
192
|
-
}
|
|
193
|
-
}
|
package/src/data/supabase.ts
DELETED
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
import { getFormsTenantId } from '../lib/tenant'
|
|
2
|
-
import type { CustomFormsDataProvider } from './types'
|
|
3
|
-
import type {
|
|
4
|
-
FormTemplate,
|
|
5
|
-
FormDocument,
|
|
6
|
-
DocumentFile,
|
|
7
|
-
TemplateQuery,
|
|
8
|
-
DocumentQuery,
|
|
9
|
-
PaginatedResult,
|
|
10
|
-
CreateTemplateInput,
|
|
11
|
-
UpdateTemplateInput,
|
|
12
|
-
CreateDocumentInput,
|
|
13
|
-
UpdateDocumentInput,
|
|
14
|
-
} from '../types'
|
|
15
|
-
import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'
|
|
16
|
-
import { T } from './tables'
|
|
17
|
-
|
|
18
|
-
function getTenantId(): string {
|
|
19
|
-
// Local override wins; else use the app's active tenant so writes pass RLS.
|
|
20
|
-
return getFormsTenantId() ?? getActiveTenantId() ?? ''
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getClient() {
|
|
24
|
-
const supabase = getSupabaseClientOptional() as any
|
|
25
|
-
if (!supabase) throw new Error('Supabase not initialized')
|
|
26
|
-
return supabase
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
function mapTemplate(r: any): FormTemplate {
|
|
31
|
-
return {
|
|
32
|
-
id: r.id,
|
|
33
|
-
tenantId: r.tenant_id,
|
|
34
|
-
name: r.name,
|
|
35
|
-
description: r.description,
|
|
36
|
-
category: r.category,
|
|
37
|
-
version: r.version,
|
|
38
|
-
isCurrent: r.is_current,
|
|
39
|
-
parentId: r.parent_id,
|
|
40
|
-
schema: r.schema,
|
|
41
|
-
specialty: r.specialty,
|
|
42
|
-
tags: r.tags ?? [],
|
|
43
|
-
metadata: r.metadata ?? {},
|
|
44
|
-
isActive: r.is_active,
|
|
45
|
-
isDeleted: r.is_deleted,
|
|
46
|
-
createdBy: r.created_by,
|
|
47
|
-
updatedBy: r.updated_by,
|
|
48
|
-
createdAt: r.created_at,
|
|
49
|
-
updatedAt: r.updated_at,
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function mapDocument(r: any): FormDocument {
|
|
54
|
-
return {
|
|
55
|
-
id: r.id,
|
|
56
|
-
tenantId: r.tenant_id,
|
|
57
|
-
templateId: r.template_id ?? '',
|
|
58
|
-
templateName: r.template_name,
|
|
59
|
-
templateCategory: r.template_category,
|
|
60
|
-
personId: r.person_id,
|
|
61
|
-
personName: r.person_name,
|
|
62
|
-
title: r.title,
|
|
63
|
-
data: r.form_data ?? {},
|
|
64
|
-
status: r.status,
|
|
65
|
-
signedAt: r.signed_at,
|
|
66
|
-
signedBy: r.signed_by,
|
|
67
|
-
notes: r.notes,
|
|
68
|
-
metadata: r.metadata ?? {},
|
|
69
|
-
isDeleted: false,
|
|
70
|
-
createdBy: r.created_by,
|
|
71
|
-
updatedBy: r.updated_by,
|
|
72
|
-
createdAt: r.created_at,
|
|
73
|
-
updatedAt: r.updated_at,
|
|
74
|
-
// Extra fields from core document
|
|
75
|
-
kind: r.kind,
|
|
76
|
-
fileUrl: r.file_url,
|
|
77
|
-
fileName: r.file_name,
|
|
78
|
-
fileSize: r.file_size,
|
|
79
|
-
mimeType: r.mime_type,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function mapFile(r: any): DocumentFile {
|
|
84
|
-
return {
|
|
85
|
-
id: r.id,
|
|
86
|
-
tenantId: r.tenant_id,
|
|
87
|
-
documentId: r.document_id,
|
|
88
|
-
fieldKey: r.field_key,
|
|
89
|
-
fileUrl: r.file_url,
|
|
90
|
-
fileName: r.file_name,
|
|
91
|
-
fileSize: r.file_size,
|
|
92
|
-
mimeType: r.mime_type,
|
|
93
|
-
sortOrder: r.sort_order,
|
|
94
|
-
metadata: r.metadata ?? {},
|
|
95
|
-
createdAt: r.created_at,
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function createSupabaseFormsProvider(): CustomFormsDataProvider {
|
|
100
|
-
return {
|
|
101
|
-
// ── Templates ──────────────────────────────────────────
|
|
102
|
-
|
|
103
|
-
async getTemplates(query: TemplateQuery): Promise<PaginatedResult<FormTemplate>> {
|
|
104
|
-
const db = getClient()
|
|
105
|
-
let qb = db
|
|
106
|
-
.from(T.templates)
|
|
107
|
-
.select('*', { count: 'exact' })
|
|
108
|
-
.eq('tenant_id', getTenantId())
|
|
109
|
-
.eq('is_current', true)
|
|
110
|
-
.eq('is_deleted', false)
|
|
111
|
-
// Archived templates (isActive=false) leave the list and the picker.
|
|
112
|
-
.eq('is_active', true)
|
|
113
|
-
|
|
114
|
-
if (query.category) qb = qb.eq('category', query.category)
|
|
115
|
-
if (query.search) qb = qb.ilike('name', `%${query.search}%`)
|
|
116
|
-
|
|
117
|
-
const page = query.page ?? 1
|
|
118
|
-
const pageSize = query.pageSize ?? 25
|
|
119
|
-
qb = qb
|
|
120
|
-
.order('updated_at', { ascending: false })
|
|
121
|
-
.range((page - 1) * pageSize, page * pageSize - 1)
|
|
122
|
-
|
|
123
|
-
const { data, count, error } = await qb
|
|
124
|
-
if (error) throw new Error(error.message)
|
|
125
|
-
return { data: (data ?? []).map(mapTemplate), total: count ?? 0 }
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
async getTemplateById(id: string): Promise<FormTemplate | null> {
|
|
129
|
-
const db = getClient()
|
|
130
|
-
const { data, error } = await db
|
|
131
|
-
.from(T.templates)
|
|
132
|
-
.select('*')
|
|
133
|
-
.eq('id', id)
|
|
134
|
-
.eq('is_deleted', false)
|
|
135
|
-
.single()
|
|
136
|
-
if (error || !data) return null
|
|
137
|
-
return mapTemplate(data)
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
async createTemplate(input: CreateTemplateInput): Promise<FormTemplate> {
|
|
141
|
-
const db = getClient()
|
|
142
|
-
const tenantId = getTenantId()
|
|
143
|
-
const { data, error } = await db
|
|
144
|
-
.from(T.templates)
|
|
145
|
-
.insert({
|
|
146
|
-
tenant_id: tenantId,
|
|
147
|
-
name: input.name,
|
|
148
|
-
description: input.description,
|
|
149
|
-
category: input.category,
|
|
150
|
-
schema: input.schema,
|
|
151
|
-
specialty: input.specialty,
|
|
152
|
-
tags: input.tags ?? [],
|
|
153
|
-
metadata: input.metadata ?? {},
|
|
154
|
-
})
|
|
155
|
-
.select()
|
|
156
|
-
.single()
|
|
157
|
-
if (error) throw new Error(error.message)
|
|
158
|
-
return mapTemplate(data)
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
async updateTemplate(id: string, input: UpdateTemplateInput): Promise<FormTemplate> {
|
|
162
|
-
const db = getClient()
|
|
163
|
-
const tenantId = getTenantId()
|
|
164
|
-
|
|
165
|
-
const { count } = await db
|
|
166
|
-
.from(T.documents)
|
|
167
|
-
.select('document_id', { count: 'exact', head: true })
|
|
168
|
-
.eq('template_id', id)
|
|
169
|
-
|
|
170
|
-
const hasDocuments = (count ?? 0) > 0
|
|
171
|
-
|
|
172
|
-
if (hasDocuments && input.schema) {
|
|
173
|
-
const existing = await this.getTemplateById(id)
|
|
174
|
-
if (!existing) throw new Error('Template not found')
|
|
175
|
-
|
|
176
|
-
const { data: newRow, error: insertError } = await db
|
|
177
|
-
.from(T.templates)
|
|
178
|
-
.insert({
|
|
179
|
-
tenant_id: tenantId,
|
|
180
|
-
name: input.name ?? existing.name,
|
|
181
|
-
description: input.description ?? existing.description,
|
|
182
|
-
category: input.category ?? existing.category,
|
|
183
|
-
schema: input.schema,
|
|
184
|
-
specialty: input.specialty ?? existing.specialty,
|
|
185
|
-
tags: input.tags ?? existing.tags,
|
|
186
|
-
metadata: input.metadata ?? existing.metadata,
|
|
187
|
-
is_active: input.isActive ?? existing.isActive,
|
|
188
|
-
version: existing.version + 1,
|
|
189
|
-
is_current: true,
|
|
190
|
-
parent_id: existing.parentId ?? existing.id,
|
|
191
|
-
})
|
|
192
|
-
.select()
|
|
193
|
-
.single()
|
|
194
|
-
if (insertError) throw new Error(insertError.message)
|
|
195
|
-
|
|
196
|
-
await db
|
|
197
|
-
.from(T.templates)
|
|
198
|
-
.update({ is_current: false, updated_at: new Date().toISOString() })
|
|
199
|
-
.eq('id', id)
|
|
200
|
-
|
|
201
|
-
return mapTemplate(newRow)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const updates: Record<string, unknown> = { updated_at: new Date().toISOString() }
|
|
205
|
-
if (input.name !== undefined) updates.name = input.name
|
|
206
|
-
if (input.description !== undefined) updates.description = input.description
|
|
207
|
-
if (input.category !== undefined) updates.category = input.category
|
|
208
|
-
if (input.schema !== undefined) updates.schema = input.schema
|
|
209
|
-
if (input.specialty !== undefined) updates.specialty = input.specialty
|
|
210
|
-
if (input.tags !== undefined) updates.tags = input.tags
|
|
211
|
-
if (input.metadata !== undefined) updates.metadata = input.metadata
|
|
212
|
-
if (input.isActive !== undefined) updates.is_active = input.isActive
|
|
213
|
-
|
|
214
|
-
const { data, error } = await db
|
|
215
|
-
.from(T.templates)
|
|
216
|
-
.update(updates)
|
|
217
|
-
.eq('id', id)
|
|
218
|
-
.select()
|
|
219
|
-
.single()
|
|
220
|
-
if (error) throw new Error(error.message)
|
|
221
|
-
return mapTemplate(data)
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
async deleteTemplate(id: string): Promise<void> {
|
|
225
|
-
const db = getClient()
|
|
226
|
-
const { error } = await db
|
|
227
|
-
.from(T.templates)
|
|
228
|
-
.update({ is_deleted: true, updated_at: new Date().toISOString() })
|
|
229
|
-
.eq('id', id)
|
|
230
|
-
if (error) throw new Error(error.message)
|
|
231
|
-
},
|
|
232
|
-
|
|
233
|
-
// ── Documents (public.documents + plg_forms_documents extension) ──
|
|
234
|
-
|
|
235
|
-
async getDocuments(query: DocumentQuery): Promise<PaginatedResult<FormDocument>> {
|
|
236
|
-
const db = getClient()
|
|
237
|
-
// Query through the v_documents view (joins core + form extension)
|
|
238
|
-
let qb = db
|
|
239
|
-
.from('v_documents')
|
|
240
|
-
.select('*', { count: 'exact' })
|
|
241
|
-
.eq('tenant_id', getTenantId())
|
|
242
|
-
.eq('is_active', true)
|
|
243
|
-
|
|
244
|
-
if (query.personId) qb = qb.eq('person_id', query.personId)
|
|
245
|
-
if (query.templateId) qb = qb.eq('template_id', query.templateId)
|
|
246
|
-
if (query.status) {
|
|
247
|
-
const statuses = Array.isArray(query.status) ? query.status : [query.status]
|
|
248
|
-
qb = qb.in('status', statuses)
|
|
249
|
-
}
|
|
250
|
-
if (query.search) qb = qb.or(`title.ilike.%${query.search}%,template_name.ilike.%${query.search}%`)
|
|
251
|
-
|
|
252
|
-
const page = query.page ?? 1
|
|
253
|
-
const pageSize = query.pageSize ?? 50
|
|
254
|
-
qb = qb
|
|
255
|
-
.order('created_at', { ascending: false })
|
|
256
|
-
.range((page - 1) * pageSize, page * pageSize - 1)
|
|
257
|
-
|
|
258
|
-
const { data, count, error } = await qb
|
|
259
|
-
if (error) throw new Error(error.message)
|
|
260
|
-
return { data: (data ?? []).map(mapDocument), total: count ?? 0 }
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
async getDocumentById(id: string): Promise<FormDocument | null> {
|
|
264
|
-
const db = getClient()
|
|
265
|
-
const { data, error } = await db
|
|
266
|
-
.from('v_documents')
|
|
267
|
-
.select('*')
|
|
268
|
-
.eq('id', id)
|
|
269
|
-
.single()
|
|
270
|
-
if (error || !data) return null
|
|
271
|
-
return mapDocument(data)
|
|
272
|
-
},
|
|
273
|
-
|
|
274
|
-
async createDocument(input: CreateDocumentInput): Promise<FormDocument> {
|
|
275
|
-
const tenantId = getTenantId()
|
|
276
|
-
const isForm = !!input.templateId
|
|
277
|
-
|
|
278
|
-
// Step 1: Insert into public.documents
|
|
279
|
-
const { data: coreDoc, error: coreError } = await getClient()
|
|
280
|
-
.from('documents')
|
|
281
|
-
.insert({
|
|
282
|
-
tenant_id: tenantId,
|
|
283
|
-
kind: input.kind ?? (isForm ? 'form' : 'attachment'),
|
|
284
|
-
person_id: input.personId,
|
|
285
|
-
title: input.title,
|
|
286
|
-
status: input.status ?? 'draft',
|
|
287
|
-
file_url: input.fileUrl,
|
|
288
|
-
file_name: input.fileName,
|
|
289
|
-
file_size: input.fileSize,
|
|
290
|
-
mime_type: input.mimeType,
|
|
291
|
-
metadata: {},
|
|
292
|
-
})
|
|
293
|
-
.select()
|
|
294
|
-
.single()
|
|
295
|
-
if (coreError) throw new Error(coreError.message)
|
|
296
|
-
|
|
297
|
-
// Step 2: For form documents, insert extension row
|
|
298
|
-
if (isForm && input.templateId) {
|
|
299
|
-
const db = getClient()
|
|
300
|
-
const { error: extError } = await db
|
|
301
|
-
.from(T.documents)
|
|
302
|
-
.insert({
|
|
303
|
-
document_id: coreDoc.id,
|
|
304
|
-
tenant_id: tenantId,
|
|
305
|
-
template_id: input.templateId,
|
|
306
|
-
data: input.data ?? {},
|
|
307
|
-
})
|
|
308
|
-
if (extError) {
|
|
309
|
-
// Rollback core insert
|
|
310
|
-
await getClient().from('documents').delete().eq('id', coreDoc.id)
|
|
311
|
-
throw new Error(extError.message)
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Fetch via view to get joined data
|
|
316
|
-
const doc = await this.getDocumentById(coreDoc.id)
|
|
317
|
-
return doc!
|
|
318
|
-
},
|
|
319
|
-
|
|
320
|
-
async updateDocument(id: string, input: UpdateDocumentInput): Promise<FormDocument> {
|
|
321
|
-
const now = new Date().toISOString()
|
|
322
|
-
|
|
323
|
-
// Update core document fields
|
|
324
|
-
const coreUpdates: Record<string, unknown> = { updated_at: now }
|
|
325
|
-
if (input.status !== undefined) coreUpdates.status = input.status
|
|
326
|
-
if (input.title !== undefined) coreUpdates.title = input.title
|
|
327
|
-
if (input.notes !== undefined) coreUpdates.notes = input.notes
|
|
328
|
-
|
|
329
|
-
const { error: coreError } = await getClient()
|
|
330
|
-
.from('documents')
|
|
331
|
-
.update(coreUpdates)
|
|
332
|
-
.eq('id', id)
|
|
333
|
-
if (coreError) throw new Error(coreError.message)
|
|
334
|
-
|
|
335
|
-
// Update form extension if data is provided
|
|
336
|
-
if (input.data !== undefined) {
|
|
337
|
-
const db = getClient()
|
|
338
|
-
await db
|
|
339
|
-
.from(T.documents)
|
|
340
|
-
.update({ data: input.data, updated_at: now })
|
|
341
|
-
.eq('document_id', id)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const doc = await this.getDocumentById(id)
|
|
345
|
-
return doc!
|
|
346
|
-
},
|
|
347
|
-
|
|
348
|
-
async deleteDocument(id: string): Promise<void> {
|
|
349
|
-
const { error } = await getClient()
|
|
350
|
-
.from('documents')
|
|
351
|
-
.update({ is_active: false, updated_at: new Date().toISOString() })
|
|
352
|
-
.eq('id', id)
|
|
353
|
-
if (error) throw new Error(error.message)
|
|
354
|
-
},
|
|
355
|
-
|
|
356
|
-
// ── Files ──────────────────────────────────────────────
|
|
357
|
-
|
|
358
|
-
async getDocumentFiles(documentId: string): Promise<DocumentFile[]> {
|
|
359
|
-
const db = getClient()
|
|
360
|
-
const { data, error } = await db
|
|
361
|
-
.from(T.documentFiles)
|
|
362
|
-
.select('*')
|
|
363
|
-
.eq('document_id', documentId)
|
|
364
|
-
.order('sort_order')
|
|
365
|
-
if (error) throw new Error(error.message)
|
|
366
|
-
return (data ?? []).map(mapFile)
|
|
367
|
-
},
|
|
368
|
-
|
|
369
|
-
async uploadFile(documentId: string, fieldKey: string, file: File): Promise<DocumentFile> {
|
|
370
|
-
const db = getClient()
|
|
371
|
-
const tenantId = getTenantId()
|
|
372
|
-
const ext = file.name.split('.').pop() ?? 'bin'
|
|
373
|
-
const storagePath = `${tenantId}/forms/${documentId}/${fieldKey}/${crypto.randomUUID()}.${ext}`
|
|
374
|
-
|
|
375
|
-
const { error: uploadError } = await db.storage
|
|
376
|
-
.from('documents')
|
|
377
|
-
.upload(storagePath, file, { contentType: file.type })
|
|
378
|
-
if (uploadError) throw new Error(uploadError.message)
|
|
379
|
-
|
|
380
|
-
const { data: urlData } = db.storage.from('documents').getPublicUrl(storagePath)
|
|
381
|
-
|
|
382
|
-
const { data, error } = await db
|
|
383
|
-
.from(T.documentFiles)
|
|
384
|
-
.insert({
|
|
385
|
-
tenant_id: tenantId,
|
|
386
|
-
document_id: documentId,
|
|
387
|
-
field_key: fieldKey,
|
|
388
|
-
file_url: urlData.publicUrl,
|
|
389
|
-
file_name: file.name,
|
|
390
|
-
file_size: file.size,
|
|
391
|
-
mime_type: file.type,
|
|
392
|
-
})
|
|
393
|
-
.select()
|
|
394
|
-
.single()
|
|
395
|
-
if (error) throw new Error(error.message)
|
|
396
|
-
return mapFile(data)
|
|
397
|
-
},
|
|
398
|
-
|
|
399
|
-
async deleteFile(fileId: string): Promise<void> {
|
|
400
|
-
const db = getClient()
|
|
401
|
-
const { error } = await db
|
|
402
|
-
.from(T.documentFiles)
|
|
403
|
-
.delete()
|
|
404
|
-
.eq('id', fileId)
|
|
405
|
-
if (error) throw new Error(error.message)
|
|
406
|
-
},
|
|
407
|
-
}
|
|
408
|
-
}
|
package/src/data/tables.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
// Central physical-table-name registry for plugin-forms. `documents` stays core.
|
|
2
|
-
export const T = {
|
|
3
|
-
documents: 'plg_forms_documents',
|
|
4
|
-
templates: 'plg_forms_templates',
|
|
5
|
-
documentFiles: 'plg_forms_document_files',
|
|
6
|
-
categories: 'plg_forms_categories',
|
|
7
|
-
} as const
|
package/src/data/types.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
FormTemplate,
|
|
3
|
-
FormDocument,
|
|
4
|
-
DocumentFile,
|
|
5
|
-
TemplateQuery,
|
|
6
|
-
DocumentQuery,
|
|
7
|
-
PaginatedResult,
|
|
8
|
-
CreateTemplateInput,
|
|
9
|
-
UpdateTemplateInput,
|
|
10
|
-
CreateDocumentInput,
|
|
11
|
-
UpdateDocumentInput,
|
|
12
|
-
} from '../types'
|
|
13
|
-
|
|
14
|
-
export interface CustomFormsDataProvider {
|
|
15
|
-
// Templates
|
|
16
|
-
getTemplates(query: TemplateQuery): Promise<PaginatedResult<FormTemplate>>
|
|
17
|
-
getTemplateById(id: string): Promise<FormTemplate | null>
|
|
18
|
-
createTemplate(input: CreateTemplateInput): Promise<FormTemplate>
|
|
19
|
-
updateTemplate(id: string, input: UpdateTemplateInput): Promise<FormTemplate>
|
|
20
|
-
deleteTemplate(id: string): Promise<void>
|
|
21
|
-
|
|
22
|
-
// Documents
|
|
23
|
-
getDocuments(query: DocumentQuery): Promise<PaginatedResult<FormDocument>>
|
|
24
|
-
getDocumentById(id: string): Promise<FormDocument | null>
|
|
25
|
-
createDocument(input: CreateDocumentInput): Promise<FormDocument>
|
|
26
|
-
updateDocument(id: string, input: UpdateDocumentInput): Promise<FormDocument>
|
|
27
|
-
deleteDocument(id: string): Promise<void>
|
|
28
|
-
|
|
29
|
-
// Files
|
|
30
|
-
getDocumentFiles(documentId: string): Promise<DocumentFile[]>
|
|
31
|
-
uploadFile(documentId: string, fieldKey: string, file: File): Promise<DocumentFile>
|
|
32
|
-
deleteFile(fileId: string): Promise<void>
|
|
33
|
-
}
|