@fayz-ai/plugin-forms 0.1.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/config.d.ts +4 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +7 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/index.cjs +49 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -35
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +8 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/views/CustomFormsSettingsTab.d.ts +1 -3
- package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -1
- package/package.json +10 -6
- package/src/config.ts +5 -1
- package/src/data/supabase.ts +20 -19
- package/src/data/tables.ts +7 -0
- package/src/index.ts +22 -4
- package/src/migrations/000_plg_rename.sql +21 -0
- package/src/migrations/001_frm_base.sql +92 -56
- package/src/migrations/002_document_archetype.sql +130 -42
- package/src/migrations/index.ts +433 -0
- package/src/types.ts +4 -4
- package/src/views/CustomFormsSettingsTab.tsx +2 -19
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@fayz-ai/plugin-forms)
|
|
6
6
|
[](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
+
**Status:** beta — pre-1.0. Core surface is stable; some backend facets (see `PLUGIN_PATTERNS.md`) are still landing. APIs may change before 1.0.
|
|
9
|
+
|
|
8
10
|
Every real business runs on forms it never wanted to build: anamnesis sheets, evolution notes, consent contracts, intake docs. plugin-forms kills that busywork. Define a template once, attach a filled document to a person, and the data lives in your tenant — not in a PDF nobody can query.
|
|
9
11
|
|
|
10
12
|
It snaps into a `defineSaas` app as the document engine. A clinic fills health anamnesis, a salon collects consent forms, a studio signs contracts — same plugin, same `frm_*` tables, different templates. Documents surface right inside the person record via a detail-tab widget, and the AI assistant can list templates and a client's documents on demand.
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PluginScope, VerticalId } from '@fayz-ai/core';
|
|
1
|
+
import type { PluginRegistryDef, PluginScope, VerticalId } from '@fayz-ai/core';
|
|
2
2
|
import type { CustomFormsDataProvider } from './data/types';
|
|
3
3
|
export interface CustomFormsPluginOptions {
|
|
4
4
|
scope?: PluginScope;
|
|
@@ -7,10 +7,13 @@ export interface CustomFormsPluginOptions {
|
|
|
7
7
|
navSection?: 'main' | 'secondary' | 'settings';
|
|
8
8
|
navPosition?: number;
|
|
9
9
|
labels?: Partial<CustomFormsLabels>;
|
|
10
|
+
/** App-owned registries shown inside Forms & Documents settings. */
|
|
11
|
+
settingsRegistries?: PluginRegistryDef[];
|
|
10
12
|
}
|
|
11
13
|
export interface CustomFormsLabels {
|
|
12
14
|
pageTitle: string;
|
|
13
15
|
settingsLabel: string;
|
|
16
|
+
settingsSubtitle: string;
|
|
14
17
|
templates: string;
|
|
15
18
|
documents: string;
|
|
16
19
|
newTemplate: string;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAE3D,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,uBAAuB,CAAA;IACtC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACnC,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;CACzC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,iBAAiB,CAAA;CAC1B;AAYD,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,iBAAiB,CAInF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAiGtD,wBAAgB,2BAA2B,IAAI,uBAAuB,CAmTrE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,CAAC;;;;;CAKJ,CAAA"}
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var chunkXLUULIVL_cjs = require('./chunk-XLUULIVL.cjs');
|
|
4
4
|
var React2 = require('react');
|
|
5
5
|
var core = require('@fayz-ai/core');
|
|
6
|
+
var saas = require('@fayz-ai/saas');
|
|
6
7
|
var zustand = require('zustand');
|
|
7
8
|
var lucideReact = require('lucide-react');
|
|
8
9
|
var ui = require('@fayz-ai/ui');
|
|
@@ -12,6 +13,14 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
12
13
|
|
|
13
14
|
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
14
15
|
|
|
16
|
+
// src/data/tables.ts
|
|
17
|
+
var T = {
|
|
18
|
+
documents: "plg_forms_documents",
|
|
19
|
+
templates: "plg_forms_templates",
|
|
20
|
+
documentFiles: "plg_forms_document_files",
|
|
21
|
+
categories: "plg_forms_categories"
|
|
22
|
+
};
|
|
23
|
+
|
|
15
24
|
// src/data/mock.ts
|
|
16
25
|
function uuid() {
|
|
17
26
|
return crypto.randomUUID();
|
|
@@ -260,7 +269,7 @@ function createSupabaseFormsProvider() {
|
|
|
260
269
|
// ── Templates ──────────────────────────────────────────
|
|
261
270
|
async getTemplates(query) {
|
|
262
271
|
const db = getClient();
|
|
263
|
-
let qb = db.from(
|
|
272
|
+
let qb = db.from(T.templates).select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_current", true).eq("is_deleted", false);
|
|
264
273
|
if (query.category) qb = qb.eq("category", query.category);
|
|
265
274
|
if (query.search) qb = qb.ilike("name", `%${query.search}%`);
|
|
266
275
|
const page = query.page ?? 1;
|
|
@@ -272,14 +281,14 @@ function createSupabaseFormsProvider() {
|
|
|
272
281
|
},
|
|
273
282
|
async getTemplateById(id) {
|
|
274
283
|
const db = getClient();
|
|
275
|
-
const { data, error } = await db.from(
|
|
284
|
+
const { data, error } = await db.from(T.templates).select("*").eq("id", id).eq("is_deleted", false).single();
|
|
276
285
|
if (error || !data) return null;
|
|
277
286
|
return mapTemplate(data);
|
|
278
287
|
},
|
|
279
288
|
async createTemplate(input) {
|
|
280
289
|
const db = getClient();
|
|
281
290
|
const tenantId = getTenantId();
|
|
282
|
-
const { data, error } = await db.from(
|
|
291
|
+
const { data, error } = await db.from(T.templates).insert({
|
|
283
292
|
tenant_id: tenantId,
|
|
284
293
|
name: input.name,
|
|
285
294
|
description: input.description,
|
|
@@ -295,12 +304,12 @@ function createSupabaseFormsProvider() {
|
|
|
295
304
|
async updateTemplate(id, input) {
|
|
296
305
|
const db = getClient();
|
|
297
306
|
const tenantId = getTenantId();
|
|
298
|
-
const { count } = await db.from(
|
|
307
|
+
const { count } = await db.from(T.documents).select("document_id", { count: "exact", head: true }).eq("template_id", id);
|
|
299
308
|
const hasDocuments = (count ?? 0) > 0;
|
|
300
309
|
if (hasDocuments && input.schema) {
|
|
301
310
|
const existing = await this.getTemplateById(id);
|
|
302
311
|
if (!existing) throw new Error("Template not found");
|
|
303
|
-
const { data: newRow, error: insertError } = await db.from(
|
|
312
|
+
const { data: newRow, error: insertError } = await db.from(T.templates).insert({
|
|
304
313
|
tenant_id: tenantId,
|
|
305
314
|
name: input.name ?? existing.name,
|
|
306
315
|
description: input.description ?? existing.description,
|
|
@@ -315,7 +324,7 @@ function createSupabaseFormsProvider() {
|
|
|
315
324
|
parent_id: existing.parentId ?? existing.id
|
|
316
325
|
}).select().single();
|
|
317
326
|
if (insertError) throw new Error(insertError.message);
|
|
318
|
-
await db.from(
|
|
327
|
+
await db.from(T.templates).update({ is_current: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
319
328
|
return mapTemplate(newRow);
|
|
320
329
|
}
|
|
321
330
|
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -327,16 +336,16 @@ function createSupabaseFormsProvider() {
|
|
|
327
336
|
if (input.tags !== void 0) updates.tags = input.tags;
|
|
328
337
|
if (input.metadata !== void 0) updates.metadata = input.metadata;
|
|
329
338
|
if (input.isActive !== void 0) updates.is_active = input.isActive;
|
|
330
|
-
const { data, error } = await db.from(
|
|
339
|
+
const { data, error } = await db.from(T.templates).update(updates).eq("id", id).select().single();
|
|
331
340
|
if (error) throw new Error(error.message);
|
|
332
341
|
return mapTemplate(data);
|
|
333
342
|
},
|
|
334
343
|
async deleteTemplate(id) {
|
|
335
344
|
const db = getClient();
|
|
336
|
-
const { error } = await db.from(
|
|
345
|
+
const { error } = await db.from(T.templates).update({ is_deleted: true, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
337
346
|
if (error) throw new Error(error.message);
|
|
338
347
|
},
|
|
339
|
-
// ── Documents (
|
|
348
|
+
// ── Documents (public.documents + plg_forms_documents extension) ──
|
|
340
349
|
async getDocuments(query) {
|
|
341
350
|
const db = getClient();
|
|
342
351
|
let qb = db.from("v_documents").select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_active", true);
|
|
@@ -363,7 +372,7 @@ function createSupabaseFormsProvider() {
|
|
|
363
372
|
async createDocument(input) {
|
|
364
373
|
const tenantId = getTenantId();
|
|
365
374
|
const isForm = !!input.templateId;
|
|
366
|
-
const { data: coreDoc, error: coreError } = await getClient().from("
|
|
375
|
+
const { data: coreDoc, error: coreError } = await getClient().from("documents").insert({
|
|
367
376
|
tenant_id: tenantId,
|
|
368
377
|
kind: input.kind ?? (isForm ? "form" : "attachment"),
|
|
369
378
|
person_id: input.personId,
|
|
@@ -378,14 +387,14 @@ function createSupabaseFormsProvider() {
|
|
|
378
387
|
if (coreError) throw new Error(coreError.message);
|
|
379
388
|
if (isForm && input.templateId) {
|
|
380
389
|
const db = getClient();
|
|
381
|
-
const { error: extError } = await db.from(
|
|
390
|
+
const { error: extError } = await db.from(T.documents).insert({
|
|
382
391
|
document_id: coreDoc.id,
|
|
383
392
|
tenant_id: tenantId,
|
|
384
393
|
template_id: input.templateId,
|
|
385
394
|
data: input.data ?? {}
|
|
386
395
|
});
|
|
387
396
|
if (extError) {
|
|
388
|
-
await getClient().from("
|
|
397
|
+
await getClient().from("documents").delete().eq("id", coreDoc.id);
|
|
389
398
|
throw new Error(extError.message);
|
|
390
399
|
}
|
|
391
400
|
}
|
|
@@ -398,23 +407,23 @@ function createSupabaseFormsProvider() {
|
|
|
398
407
|
if (input.status !== void 0) coreUpdates.status = input.status;
|
|
399
408
|
if (input.title !== void 0) coreUpdates.title = input.title;
|
|
400
409
|
if (input.notes !== void 0) coreUpdates.notes = input.notes;
|
|
401
|
-
const { error: coreError } = await getClient().from("
|
|
410
|
+
const { error: coreError } = await getClient().from("documents").update(coreUpdates).eq("id", id);
|
|
402
411
|
if (coreError) throw new Error(coreError.message);
|
|
403
412
|
if (input.data !== void 0) {
|
|
404
413
|
const db = getClient();
|
|
405
|
-
await db.from(
|
|
414
|
+
await db.from(T.documents).update({ data: input.data, updated_at: now2 }).eq("document_id", id);
|
|
406
415
|
}
|
|
407
416
|
const doc = await this.getDocumentById(id);
|
|
408
417
|
return doc;
|
|
409
418
|
},
|
|
410
419
|
async deleteDocument(id) {
|
|
411
|
-
const { error } = await getClient().from("
|
|
420
|
+
const { error } = await getClient().from("documents").update({ is_active: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
412
421
|
if (error) throw new Error(error.message);
|
|
413
422
|
},
|
|
414
423
|
// ── Files ──────────────────────────────────────────────
|
|
415
424
|
async getDocumentFiles(documentId) {
|
|
416
425
|
const db = getClient();
|
|
417
|
-
const { data, error } = await db.from(
|
|
426
|
+
const { data, error } = await db.from(T.documentFiles).select("*").eq("document_id", documentId).order("sort_order");
|
|
418
427
|
if (error) throw new Error(error.message);
|
|
419
428
|
return (data ?? []).map(mapFile);
|
|
420
429
|
},
|
|
@@ -426,7 +435,7 @@ function createSupabaseFormsProvider() {
|
|
|
426
435
|
const { error: uploadError } = await db.storage.from("documents").upload(storagePath, file, { contentType: file.type });
|
|
427
436
|
if (uploadError) throw new Error(uploadError.message);
|
|
428
437
|
const { data: urlData } = db.storage.from("documents").getPublicUrl(storagePath);
|
|
429
|
-
const { data, error } = await db.from(
|
|
438
|
+
const { data, error } = await db.from(T.documentFiles).insert({
|
|
430
439
|
tenant_id: tenantId,
|
|
431
440
|
document_id: documentId,
|
|
432
441
|
field_key: fieldKey,
|
|
@@ -440,7 +449,7 @@ function createSupabaseFormsProvider() {
|
|
|
440
449
|
},
|
|
441
450
|
async deleteFile(fileId) {
|
|
442
451
|
const db = getClient();
|
|
443
|
-
const { error } = await db.from(
|
|
452
|
+
const { error } = await db.from(T.documentFiles).delete().eq("id", fileId);
|
|
444
453
|
if (error) throw new Error(error.message);
|
|
445
454
|
}
|
|
446
455
|
};
|
|
@@ -700,6 +709,7 @@ var customFormsLocales = {
|
|
|
700
709
|
var DEFAULT_LABELS = {
|
|
701
710
|
pageTitle: "Custom Forms",
|
|
702
711
|
settingsLabel: "Forms & Documents",
|
|
712
|
+
settingsSubtitle: "Create and manage custom forms for your business",
|
|
703
713
|
templates: "Templates",
|
|
704
714
|
documents: "Documents",
|
|
705
715
|
newTemplate: "New Template",
|
|
@@ -821,8 +831,7 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
821
831
|
] }) }, i)) })
|
|
822
832
|
] });
|
|
823
833
|
}
|
|
824
|
-
function CustomFormsSettingsTab({ config, provider, store
|
|
825
|
-
const t = core.useTranslation();
|
|
834
|
+
function CustomFormsSettingsTab({ config, provider, store }) {
|
|
826
835
|
const [builderTemplateId, setBuilderTemplateId] = React2.useState(null);
|
|
827
836
|
const handleNew = React2.useCallback(() => {
|
|
828
837
|
setBuilderTemplateId("__new__");
|
|
@@ -846,7 +855,7 @@ function CustomFormsSettingsTab({ config, provider, store, registries }) {
|
|
|
846
855
|
}
|
|
847
856
|
checkHash();
|
|
848
857
|
}, []);
|
|
849
|
-
|
|
858
|
+
return builderTemplateId ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
850
859
|
BuilderWrapper,
|
|
851
860
|
{
|
|
852
861
|
templateId: builderTemplateId === "__new__" ? void 0 : builderTemplateId,
|
|
@@ -864,13 +873,6 @@ function CustomFormsSettingsTab({ config, provider, store, registries }) {
|
|
|
864
873
|
onNew: handleNew
|
|
865
874
|
}
|
|
866
875
|
);
|
|
867
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "24px" }, children: [
|
|
868
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "16px" }, children: [
|
|
869
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "20px", fontWeight: 600, margin: 0 }, children: t("customForms.settingsTitle") }),
|
|
870
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--muted-foreground, #6b7280)", margin: "4px 0 0", fontSize: "14px" }, children: t("customForms.settingsSubtitle") })
|
|
871
|
-
] }),
|
|
872
|
-
templatesContent
|
|
873
|
-
] });
|
|
874
876
|
}
|
|
875
877
|
function BuilderWrapper({
|
|
876
878
|
templateId,
|
|
@@ -1505,7 +1507,7 @@ function createCustomFormsPlugin(options) {
|
|
|
1505
1507
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true, inlineToggle: true }
|
|
1506
1508
|
],
|
|
1507
1509
|
data: {
|
|
1508
|
-
table:
|
|
1510
|
+
table: T.categories,
|
|
1509
1511
|
tenantScoped: true
|
|
1510
1512
|
}
|
|
1511
1513
|
},
|
|
@@ -1520,8 +1522,23 @@ function createCustomFormsPlugin(options) {
|
|
|
1520
1522
|
]
|
|
1521
1523
|
}
|
|
1522
1524
|
];
|
|
1523
|
-
const
|
|
1524
|
-
SettingsComponent
|
|
1525
|
+
const registries = [...formRegistries, ...options?.settingsRegistries ?? []];
|
|
1526
|
+
const SettingsComponent = () => React2__default.default.createElement(saas.PluginSettingsPanel, {
|
|
1527
|
+
title: config.labels.settingsLabel,
|
|
1528
|
+
subtitle: config.labels.settingsSubtitle,
|
|
1529
|
+
customTabs: [
|
|
1530
|
+
{
|
|
1531
|
+
id: "forms",
|
|
1532
|
+
label: config.labels.pageTitle,
|
|
1533
|
+
icon: "FileText",
|
|
1534
|
+
content: React2__default.default.createElement(CustomFormsSettingsTab, { config, provider, store })
|
|
1535
|
+
}
|
|
1536
|
+
],
|
|
1537
|
+
registries,
|
|
1538
|
+
routeBase: "/settings/custom_forms",
|
|
1539
|
+
hostPluginId: "custom_forms"
|
|
1540
|
+
});
|
|
1541
|
+
SettingsComponent.displayName = "CustomFormsSettingsPanel";
|
|
1525
1542
|
const PersonDocumentsWidget2 = (props) => React2__default.default.createElement(PersonDocumentsWidget, { ...props, config, provider, store });
|
|
1526
1543
|
PersonDocumentsWidget2.displayName = "PersonDocumentsWidget";
|
|
1527
1544
|
return {
|
|
@@ -1555,7 +1572,7 @@ function createCustomFormsPlugin(options) {
|
|
|
1555
1572
|
order: 0
|
|
1556
1573
|
}
|
|
1557
1574
|
],
|
|
1558
|
-
registries
|
|
1575
|
+
registries,
|
|
1559
1576
|
aiTools: [
|
|
1560
1577
|
{
|
|
1561
1578
|
id: "custom_forms.list-templates",
|