@nextsparkjs/theme-crm 0.1.0-beta.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 (140) hide show
  1. package/CRM_PLAN.md +343 -0
  2. package/about.md +122 -0
  3. package/config/app.config.ts +185 -0
  4. package/config/billing.config.ts +187 -0
  5. package/config/dashboard.config.ts +372 -0
  6. package/config/dev.config.ts +55 -0
  7. package/config/features.config.ts +336 -0
  8. package/config/flows.config.ts +511 -0
  9. package/config/permissions.config.ts +297 -0
  10. package/config/theme.config.ts +111 -0
  11. package/entities/activities/activities.config.ts +61 -0
  12. package/entities/activities/activities.fields.ts +362 -0
  13. package/entities/activities/activities.service.ts +503 -0
  14. package/entities/activities/activities.types.ts +117 -0
  15. package/entities/activities/messages/en.json +123 -0
  16. package/entities/activities/messages/es.json +123 -0
  17. package/entities/activities/migrations/020_activities_table.sql +123 -0
  18. package/entities/activities/migrations/021_activities_metas.sql +114 -0
  19. package/entities/activities/migrations/022_activities_sample_data.sql +420 -0
  20. package/entities/campaigns/campaigns.config.ts +61 -0
  21. package/entities/campaigns/campaigns.fields.ts +413 -0
  22. package/entities/campaigns/campaigns.service.ts +426 -0
  23. package/entities/campaigns/campaigns.types.ts +124 -0
  24. package/entities/campaigns/messages/en.json +145 -0
  25. package/entities/campaigns/messages/es.json +145 -0
  26. package/entities/campaigns/migrations/001_campaigns_table.sql +127 -0
  27. package/entities/campaigns/migrations/002_campaigns_metas.sql +114 -0
  28. package/entities/campaigns/migrations/003_campaigns_sample_data.sql +364 -0
  29. package/entities/companies/companies.config.ts +61 -0
  30. package/entities/companies/companies.fields.ts +429 -0
  31. package/entities/companies/companies.service.ts +566 -0
  32. package/entities/companies/companies.types.ts +125 -0
  33. package/entities/companies/messages/en.json +146 -0
  34. package/entities/companies/messages/es.json +146 -0
  35. package/entities/companies/migrations/001_companies_table.sql +150 -0
  36. package/entities/companies/migrations/002_companies_metas.sql +114 -0
  37. package/entities/companies/migrations/003_companies_sample_data.sql +246 -0
  38. package/entities/contacts/contacts.config.ts +61 -0
  39. package/entities/contacts/contacts.fields.ts +359 -0
  40. package/entities/contacts/contacts.service.ts +509 -0
  41. package/entities/contacts/contacts.types.ts +108 -0
  42. package/entities/contacts/messages/en.json +117 -0
  43. package/entities/contacts/messages/es.json +117 -0
  44. package/entities/contacts/migrations/001_contacts_table.sql +134 -0
  45. package/entities/contacts/migrations/002_contacts_metas.sql +114 -0
  46. package/entities/contacts/migrations/003_contacts_sample_data.sql +421 -0
  47. package/entities/leads/leads.config.ts +61 -0
  48. package/entities/leads/leads.fields.ts +336 -0
  49. package/entities/leads/leads.service.ts +496 -0
  50. package/entities/leads/leads.types.ts +114 -0
  51. package/entities/leads/messages/en.json +132 -0
  52. package/entities/leads/messages/es.json +132 -0
  53. package/entities/leads/migrations/001_leads_table.sql +150 -0
  54. package/entities/leads/migrations/002_leads_metas.sql +120 -0
  55. package/entities/leads/migrations/003_leads_sample_data.sql +242 -0
  56. package/entities/notes/messages/en.json +114 -0
  57. package/entities/notes/messages/es.json +114 -0
  58. package/entities/notes/migrations/020_notes_table.sql +118 -0
  59. package/entities/notes/migrations/021_notes_metas.sql +114 -0
  60. package/entities/notes/migrations/022_notes_sample_data.sql +275 -0
  61. package/entities/notes/notes.config.ts +61 -0
  62. package/entities/notes/notes.fields.ts +283 -0
  63. package/entities/notes/notes.service.ts +320 -0
  64. package/entities/notes/notes.types.ts +102 -0
  65. package/entities/opportunities/messages/en.json +107 -0
  66. package/entities/opportunities/messages/es.json +107 -0
  67. package/entities/opportunities/migrations/010_opportunities_table.sql +145 -0
  68. package/entities/opportunities/migrations/011_opportunities_metas.sql +114 -0
  69. package/entities/opportunities/migrations/012_opportunities_sample_data.sql +438 -0
  70. package/entities/opportunities/opportunities.config.ts +61 -0
  71. package/entities/opportunities/opportunities.fields.ts +416 -0
  72. package/entities/opportunities/opportunities.service.ts +525 -0
  73. package/entities/opportunities/opportunities.types.ts +135 -0
  74. package/entities/pipelines/messages/en.json +115 -0
  75. package/entities/pipelines/messages/es.json +115 -0
  76. package/entities/pipelines/migrations/001_pipelines_table.sql +106 -0
  77. package/entities/pipelines/migrations/002_pipelines_metas.sql +114 -0
  78. package/entities/pipelines/migrations/003_pipelines_sample_data.sql +91 -0
  79. package/entities/pipelines/pipelines.config.ts +62 -0
  80. package/entities/pipelines/pipelines.fields.ts +193 -0
  81. package/entities/pipelines/pipelines.service.ts +383 -0
  82. package/entities/pipelines/pipelines.types.ts +78 -0
  83. package/entities/products/messages/en.json +135 -0
  84. package/entities/products/messages/es.json +135 -0
  85. package/entities/products/migrations/001_products_table.sql +117 -0
  86. package/entities/products/migrations/002_products_metas.sql +114 -0
  87. package/entities/products/migrations/003_products_sample_data.sql +247 -0
  88. package/entities/products/products.config.ts +62 -0
  89. package/entities/products/products.fields.ts +361 -0
  90. package/entities/products/products.service.ts +437 -0
  91. package/entities/products/products.types.ts +125 -0
  92. package/lib/crm-constants.ts +77 -0
  93. package/lib/crm-utils.ts +185 -0
  94. package/lib/selectors.ts +333 -0
  95. package/messages/en.json +131 -0
  96. package/messages/es.json +131 -0
  97. package/migrations/999_theme_sample_data.sql +473 -0
  98. package/package.json +18 -0
  99. package/pendings.md +205 -0
  100. package/permissions-matrix.md +216 -0
  101. package/styles/components.css +414 -0
  102. package/styles/crm-theme.css +358 -0
  103. package/styles/globals.css +576 -0
  104. package/styles/variables.css +111 -0
  105. package/templates/dashboard/(main)/activities/components/ActivityCard.tsx +169 -0
  106. package/templates/dashboard/(main)/activities/components/ActivityTimeline.tsx +165 -0
  107. package/templates/dashboard/(main)/activities/page.tsx +297 -0
  108. package/templates/dashboard/(main)/campaigns/page.tsx +373 -0
  109. package/templates/dashboard/(main)/companies/page.tsx +296 -0
  110. package/templates/dashboard/(main)/contacts/page.tsx +347 -0
  111. package/templates/dashboard/(main)/layout.tsx +98 -0
  112. package/templates/dashboard/(main)/leads/page.tsx +335 -0
  113. package/templates/dashboard/(main)/opportunities/[id]/edit/page.tsx +95 -0
  114. package/templates/dashboard/(main)/opportunities/create/page.tsx +94 -0
  115. package/templates/dashboard/(main)/opportunities/page.tsx +350 -0
  116. package/templates/dashboard/(main)/pipelines/[id]/edit/page.tsx +95 -0
  117. package/templates/dashboard/(main)/pipelines/[id]/page.tsx +143 -0
  118. package/templates/dashboard/(main)/pipelines/create/page.tsx +94 -0
  119. package/templates/dashboard/(main)/pipelines/page.tsx +234 -0
  120. package/templates/dashboard/(main)/products/[id]/edit/page.tsx +97 -0
  121. package/templates/dashboard/(main)/products/[id]/page.tsx +509 -0
  122. package/templates/dashboard/(main)/products/create/page.tsx +96 -0
  123. package/templates/dashboard/(main)/products/page.tsx +308 -0
  124. package/templates/shared/ActionButtons.tsx +41 -0
  125. package/templates/shared/CRMDashboard.tsx +519 -0
  126. package/templates/shared/CRMDataTable.tsx +441 -0
  127. package/templates/shared/CRMMetricCard.tsx +76 -0
  128. package/templates/shared/CRMMobileNav.tsx +172 -0
  129. package/templates/shared/CRMSidebar.tsx +346 -0
  130. package/templates/shared/CRMTopBar.tsx +265 -0
  131. package/templates/shared/DealCard.tsx +123 -0
  132. package/templates/shared/EntityCard.tsx +58 -0
  133. package/templates/shared/OpportunityForm.tsx +649 -0
  134. package/templates/shared/PipelineForm.tsx +367 -0
  135. package/templates/shared/PipelineKanban.tsx +194 -0
  136. package/templates/shared/QuickFilters.tsx +47 -0
  137. package/templates/shared/StageColumn.tsx +175 -0
  138. package/templates/shared/StageSelect.tsx +177 -0
  139. package/templates/shared/StagesRepeater.tsx +317 -0
  140. package/templates/shared/index.ts +9 -0
@@ -0,0 +1,115 @@
1
+ {
2
+ "entity": {
3
+ "name": "Pipeline",
4
+ "plural": "Pipelines",
5
+ "description": "Manage sales pipelines and stages"
6
+ },
7
+ "fields": {
8
+ "name": {
9
+ "label": "Pipeline Name",
10
+ "description": "Pipeline name or title",
11
+ "placeholder": "Enter pipeline name..."
12
+ },
13
+ "description": {
14
+ "label": "Description",
15
+ "description": "Pipeline description",
16
+ "placeholder": "Enter description..."
17
+ },
18
+ "type": {
19
+ "label": "Pipeline Type",
20
+ "description": "Type of pipeline",
21
+ "placeholder": "Select type..."
22
+ },
23
+ "isDefault": {
24
+ "label": "Default Pipeline",
25
+ "description": "Whether this is the default pipeline"
26
+ },
27
+ "isActive": {
28
+ "label": "Active",
29
+ "description": "Whether pipeline is active"
30
+ },
31
+ "stages": {
32
+ "label": "Stages",
33
+ "description": "Pipeline stages configuration"
34
+ },
35
+ "conversion": {
36
+ "label": "Conversion Rate (%)",
37
+ "description": "Overall conversion rate",
38
+ "placeholder": "0.00"
39
+ },
40
+ "averageDealSize": {
41
+ "label": "Average Deal Size",
42
+ "description": "Average deal size in pipeline",
43
+ "placeholder": "0.00"
44
+ },
45
+ "averageCycleTime": {
46
+ "label": "Average Cycle Time (days)",
47
+ "description": "Average time to close deals",
48
+ "placeholder": "0"
49
+ },
50
+ "totalValue": {
51
+ "label": "Total Pipeline Value",
52
+ "description": "Total value of opportunities"
53
+ },
54
+ "totalOpportunities": {
55
+ "label": "Total Opportunities",
56
+ "description": "Number of opportunities in pipeline"
57
+ },
58
+ "winRate": {
59
+ "label": "Win Rate (%)",
60
+ "description": "Pipeline win rate percentage",
61
+ "placeholder": "0.00"
62
+ },
63
+ "createdBy": {
64
+ "label": "Created By",
65
+ "description": "User who created the pipeline",
66
+ "placeholder": "Select user..."
67
+ },
68
+ "assignedTo": {
69
+ "label": "Pipeline Owner",
70
+ "description": "User managing this pipeline",
71
+ "placeholder": "Select user..."
72
+ },
73
+ "createdAt": {
74
+ "label": "Created At",
75
+ "description": "When the pipeline was created"
76
+ },
77
+ "updatedAt": {
78
+ "label": "Updated At",
79
+ "description": "When the pipeline was last updated"
80
+ }
81
+ },
82
+ "options": {
83
+ "type": {
84
+ "sales": "Sales Pipeline",
85
+ "marketing": "Marketing Pipeline",
86
+ "support": "Support Pipeline",
87
+ "project": "Project Pipeline",
88
+ "recruitment": "Recruitment Pipeline",
89
+ "custom": "Custom Pipeline"
90
+ }
91
+ },
92
+ "actions": {
93
+ "create": "Create Pipeline",
94
+ "edit": "Edit Pipeline",
95
+ "delete": "Delete Pipeline",
96
+ "view": "View Pipeline",
97
+ "list": "List Pipelines",
98
+ "duplicate": "Duplicate Pipeline",
99
+ "activate": "Activate Pipeline",
100
+ "deactivate": "Deactivate Pipeline",
101
+ "setDefault": "Set as Default",
102
+ "reorder": "Reorder Stages"
103
+ },
104
+ "messages": {
105
+ "created": "Pipeline created successfully",
106
+ "updated": "Pipeline updated successfully",
107
+ "deleted": "Pipeline deleted successfully",
108
+ "activated": "Pipeline activated successfully",
109
+ "deactivated": "Pipeline deactivated successfully",
110
+ "setAsDefault": "Pipeline set as default successfully",
111
+ "stagesReordered": "Pipeline stages reordered successfully",
112
+ "notFound": "Pipeline not found",
113
+ "error": "An error occurred while processing the pipeline"
114
+ }
115
+ }
@@ -0,0 +1,115 @@
1
+ {
2
+ "entity": {
3
+ "name": "Pipeline",
4
+ "plural": "Pipelines",
5
+ "description": "Gestiona pipelines de ventas y etapas"
6
+ },
7
+ "fields": {
8
+ "name": {
9
+ "label": "Nombre del Pipeline",
10
+ "description": "Nombre o título del pipeline",
11
+ "placeholder": "Ingrese nombre..."
12
+ },
13
+ "description": {
14
+ "label": "Descripción",
15
+ "description": "Descripción del pipeline",
16
+ "placeholder": "Ingrese descripción..."
17
+ },
18
+ "type": {
19
+ "label": "Tipo",
20
+ "description": "Tipo de pipeline",
21
+ "placeholder": "Seleccionar tipo..."
22
+ },
23
+ "isDefault": {
24
+ "label": "Pipeline por Defecto",
25
+ "description": "Si es el pipeline por defecto"
26
+ },
27
+ "isActive": {
28
+ "label": "Activo",
29
+ "description": "Si el pipeline está activo"
30
+ },
31
+ "stages": {
32
+ "label": "Etapas",
33
+ "description": "Configuración de etapas"
34
+ },
35
+ "conversion": {
36
+ "label": "Tasa de Conversión (%)",
37
+ "description": "Tasa de conversión general",
38
+ "placeholder": "0.00"
39
+ },
40
+ "averageDealSize": {
41
+ "label": "Tamaño Promedio de Trato",
42
+ "description": "Tamaño promedio de trato",
43
+ "placeholder": "0.00"
44
+ },
45
+ "averageCycleTime": {
46
+ "label": "Tiempo Promedio (días)",
47
+ "description": "Tiempo promedio para cerrar",
48
+ "placeholder": "0"
49
+ },
50
+ "totalValue": {
51
+ "label": "Valor Total",
52
+ "description": "Valor total de oportunidades"
53
+ },
54
+ "totalOpportunities": {
55
+ "label": "Total de Oportunidades",
56
+ "description": "Número de oportunidades"
57
+ },
58
+ "winRate": {
59
+ "label": "Tasa de Éxito (%)",
60
+ "description": "Porcentaje de éxito",
61
+ "placeholder": "0.00"
62
+ },
63
+ "createdBy": {
64
+ "label": "Creado por",
65
+ "description": "Usuario que creó el pipeline",
66
+ "placeholder": "Seleccionar usuario..."
67
+ },
68
+ "assignedTo": {
69
+ "label": "Responsable",
70
+ "description": "Usuario responsable",
71
+ "placeholder": "Seleccionar usuario..."
72
+ },
73
+ "createdAt": {
74
+ "label": "Creado el",
75
+ "description": "Cuándo se creó el pipeline"
76
+ },
77
+ "updatedAt": {
78
+ "label": "Actualizado el",
79
+ "description": "Cuándo se actualizó por última vez"
80
+ }
81
+ },
82
+ "options": {
83
+ "type": {
84
+ "sales": "Pipeline de Ventas",
85
+ "marketing": "Pipeline de Marketing",
86
+ "support": "Pipeline de Soporte",
87
+ "project": "Pipeline de Proyectos",
88
+ "recruitment": "Pipeline de Reclutamiento",
89
+ "custom": "Pipeline Personalizado"
90
+ }
91
+ },
92
+ "actions": {
93
+ "create": "Crear Pipeline",
94
+ "edit": "Editar Pipeline",
95
+ "delete": "Eliminar Pipeline",
96
+ "view": "Ver Pipeline",
97
+ "list": "Listar Pipelines",
98
+ "duplicate": "Duplicar Pipeline",
99
+ "activate": "Activar Pipeline",
100
+ "deactivate": "Desactivar Pipeline",
101
+ "setDefault": "Establecer por Defecto",
102
+ "reorder": "Reordenar Etapas"
103
+ },
104
+ "messages": {
105
+ "created": "Pipeline creado exitosamente",
106
+ "updated": "Pipeline actualizado exitosamente",
107
+ "deleted": "Pipeline eliminado exitosamente",
108
+ "activated": "Pipeline activado exitosamente",
109
+ "deactivated": "Pipeline desactivado exitosamente",
110
+ "setAsDefault": "Pipeline establecido como predeterminado",
111
+ "stagesReordered": "Etapas reordenadas exitosamente",
112
+ "notFound": "Pipeline no encontrado",
113
+ "error": "Error al procesar el pipeline"
114
+ }
115
+ }
@@ -0,0 +1,106 @@
1
+ -- ============================================================================
2
+ -- Pipelines Table Migration
3
+ -- CRM theme: Sales pipelines with stages
4
+ -- Updated with team support and RLS
5
+ -- ============================================================================
6
+
7
+ -- ============================================
8
+ -- ENUM TYPES
9
+ -- ============================================
10
+ DO $$ BEGIN
11
+ CREATE TYPE pipeline_type AS ENUM ('sales', 'support', 'project', 'custom');
12
+ EXCEPTION
13
+ WHEN duplicate_object THEN null;
14
+ END $$;
15
+
16
+ -- ============================================
17
+ -- TABLE
18
+ -- ============================================
19
+ CREATE TABLE IF NOT EXISTS "pipelines" (
20
+ "id" TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
21
+
22
+ -- Pipeline info
23
+ "name" VARCHAR(255) NOT NULL,
24
+ "description" TEXT,
25
+ "type" pipeline_type DEFAULT 'sales',
26
+ "isDefault" BOOLEAN DEFAULT false,
27
+ "isActive" BOOLEAN DEFAULT true,
28
+
29
+ -- Stages as JSONB
30
+ -- Example: [{"order": 1, "name": "Qualification", "probability": 10, "color": "#3B82F6"}]
31
+ "stages" JSONB NOT NULL DEFAULT '[
32
+ {"order": 1, "name": "Qualification", "probability": 10, "color": "#3B82F6"},
33
+ {"order": 2, "name": "Needs Analysis", "probability": 25, "color": "#10B981"},
34
+ {"order": 3, "name": "Proposal", "probability": 50, "color": "#F59E0B"},
35
+ {"order": 4, "name": "Negotiation", "probability": 75, "color": "#8B5CF6"},
36
+ {"order": 5, "name": "Closed Won", "probability": 100, "color": "#059669"},
37
+ {"order": 6, "name": "Closed Lost", "probability": 0, "color": "#EF4444"}
38
+ ]',
39
+
40
+ -- Deal rotten days
41
+ "dealRottenDays" INTEGER DEFAULT 30,
42
+
43
+ -- Ownership
44
+ "userId" TEXT NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
45
+ "teamId" TEXT NOT NULL REFERENCES "teams"("id") ON DELETE CASCADE,
46
+
47
+ -- Timestamps
48
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
49
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
50
+ );
51
+
52
+ -- ============================================
53
+ -- INDEXES
54
+ -- ============================================
55
+ CREATE INDEX IF NOT EXISTS "pipelines_teamId_idx" ON "pipelines" ("teamId");
56
+ CREATE INDEX IF NOT EXISTS "pipelines_userId_idx" ON "pipelines" ("userId");
57
+ CREATE INDEX IF NOT EXISTS "pipelines_isDefault_idx" ON "pipelines" ("teamId", "isDefault") WHERE "isDefault" = true;
58
+ CREATE INDEX IF NOT EXISTS "pipelines_isActive_idx" ON "pipelines" ("isActive") WHERE "isActive" = true;
59
+ CREATE INDEX IF NOT EXISTS "pipelines_stages_idx" ON "pipelines" USING GIN ("stages");
60
+
61
+ -- ============================================
62
+ -- RLS
63
+ -- ============================================
64
+ ALTER TABLE "pipelines" ENABLE ROW LEVEL SECURITY;
65
+
66
+ DROP POLICY IF EXISTS "pipelines_select_policy" ON "pipelines";
67
+ DROP POLICY IF EXISTS "pipelines_insert_policy" ON "pipelines";
68
+ DROP POLICY IF EXISTS "pipelines_update_policy" ON "pipelines";
69
+ DROP POLICY IF EXISTS "pipelines_delete_policy" ON "pipelines";
70
+
71
+ CREATE POLICY "pipelines_select_policy" ON "pipelines"
72
+ FOR SELECT
73
+ USING ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin());
74
+
75
+ CREATE POLICY "pipelines_insert_policy" ON "pipelines"
76
+ FOR INSERT
77
+ WITH CHECK ("teamId" = ANY(public.get_user_team_ids()));
78
+
79
+ CREATE POLICY "pipelines_update_policy" ON "pipelines"
80
+ FOR UPDATE
81
+ USING ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin());
82
+
83
+ CREATE POLICY "pipelines_delete_policy" ON "pipelines"
84
+ FOR DELETE
85
+ USING ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin());
86
+
87
+ -- ============================================
88
+ -- TRIGGER updatedAt
89
+ -- ============================================
90
+ CREATE OR REPLACE FUNCTION update_pipelines_updated_at()
91
+ RETURNS TRIGGER AS $$
92
+ BEGIN
93
+ NEW."updatedAt" = NOW();
94
+ RETURN NEW;
95
+ END;
96
+ $$ LANGUAGE plpgsql;
97
+
98
+ DROP TRIGGER IF EXISTS pipelines_updated_at_trigger ON "pipelines";
99
+ CREATE TRIGGER pipelines_updated_at_trigger
100
+ BEFORE UPDATE ON "pipelines"
101
+ FOR EACH ROW
102
+ EXECUTE FUNCTION update_pipelines_updated_at();
103
+
104
+ COMMENT ON TABLE "pipelines" IS 'Sales pipelines with stages';
105
+ COMMENT ON COLUMN "pipelines"."stages" IS 'JSONB array of pipeline stages with order, name, probability, color';
106
+ COMMENT ON COLUMN "pipelines"."dealRottenDays" IS 'Days until a deal is considered stale';
@@ -0,0 +1,114 @@
1
+ -- Migration: 002_pipelines_metas.sql
2
+ -- Description: Contacts metas (table, indexes, RLS)
3
+ -- Date: 2025-09-27
4
+
5
+ -- ============================================
6
+ -- TABLE
7
+ -- ============================================
8
+ -- No DROP needed - removed automatically by parent table CASCADE
9
+ CREATE TABLE IF NOT EXISTS public."pipelines_metas" (
10
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
11
+ "entityId" TEXT NOT NULL REFERENCES public."pipelines"(id) ON DELETE CASCADE,
12
+ "metaKey" TEXT NOT NULL,
13
+ "metaValue" JSONB NOT NULL DEFAULT '{}'::jsonb,
14
+ "dataType" TEXT DEFAULT 'json',
15
+ "isPublic" BOOLEAN NOT NULL DEFAULT false,
16
+ "isSearchable" BOOLEAN NOT NULL DEFAULT false,
17
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
18
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
19
+ CONSTRAINT pipelines_metas_unique_key UNIQUE ("entityId", "metaKey")
20
+ );
21
+
22
+ COMMENT ON TABLE public."pipelines_metas" IS 'Contacts metadata table - stores additional key-value pairs for pipelines';
23
+ COMMENT ON COLUMN public."pipelines_metas"."entityId" IS 'Generic foreign key to parent pipeline entity';
24
+ COMMENT ON COLUMN public."pipelines_metas"."metaKey" IS 'Metadata key name';
25
+ COMMENT ON COLUMN public."pipelines_metas"."metaValue" IS 'Metadata value as JSONB';
26
+ COMMENT ON COLUMN public."pipelines_metas"."dataType" IS 'Type hint for the value: json, string, number, boolean';
27
+ COMMENT ON COLUMN public."pipelines_metas"."isPublic" IS 'Whether this metadata is publicly readable';
28
+ COMMENT ON COLUMN public."pipelines_metas"."isSearchable" IS 'Whether this metadata is searchable';
29
+
30
+ -- ============================================
31
+ -- TRIGGER updatedAt (uses Better Auth function)
32
+ -- ============================================
33
+ DROP TRIGGER IF EXISTS pipelines_metas_set_updated_at ON public."pipelines_metas";
34
+ CREATE TRIGGER pipelines_metas_set_updated_at
35
+ BEFORE UPDATE ON public."pipelines_metas"
36
+ FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
37
+
38
+ -- ============================================
39
+ -- INDEXES
40
+ -- ============================================
41
+ CREATE INDEX IF NOT EXISTS idx_pipelines_metas_entity_id ON public."pipelines_metas"("entityId");
42
+ CREATE INDEX IF NOT EXISTS idx_pipelines_metas_key ON public."pipelines_metas"("metaKey");
43
+ CREATE INDEX IF NOT EXISTS idx_pipelines_metas_composite ON public."pipelines_metas"("entityId", "metaKey", "isPublic");
44
+ CREATE INDEX IF NOT EXISTS idx_pipelines_metas_is_public ON public."pipelines_metas"("isPublic") WHERE "isPublic" = true;
45
+ CREATE INDEX IF NOT EXISTS idx_pipelines_metas_is_searchable ON public."pipelines_metas"("isSearchable") WHERE "isSearchable" = true;
46
+ CREATE INDEX IF NOT EXISTS idx_pipelines_metas_searchable_key ON public."pipelines_metas"("metaKey") WHERE "isSearchable" = true;
47
+ CREATE INDEX IF NOT EXISTS idx_pipelines_metas_value_gin ON public."pipelines_metas" USING GIN ("metaValue");
48
+ CREATE INDEX IF NOT EXISTS idx_pipelines_metas_value_ops ON public."pipelines_metas" USING GIN ("metaValue" jsonb_path_ops);
49
+
50
+ -- ============================================
51
+ -- RLS
52
+ -- ============================================
53
+ ALTER TABLE public."pipelines_metas" ENABLE ROW LEVEL SECURITY;
54
+
55
+ -- Cleanup existing policies
56
+ DROP POLICY IF EXISTS "Users can view pipeline metas" ON public."pipelines_metas";
57
+ DROP POLICY IF EXISTS "Users can create pipeline metas" ON public."pipelines_metas";
58
+ DROP POLICY IF EXISTS "Users can update pipeline metas" ON public."pipelines_metas";
59
+ DROP POLICY IF EXISTS "Users can delete pipeline metas" ON public."pipelines_metas";
60
+
61
+ -- ============================
62
+ -- AUTHENTICATED USER POLICIES
63
+ -- ============================
64
+ -- Inherit permissions from parent entity
65
+ CREATE POLICY "Users can view pipeline metas"
66
+ ON public."pipelines_metas"
67
+ FOR SELECT TO authenticated
68
+ USING (
69
+ EXISTS (
70
+ SELECT 1 FROM public."pipelines" c
71
+ WHERE c.id = "entityId"
72
+ AND c."userId" = public.get_auth_user_id()
73
+ )
74
+ );
75
+
76
+ CREATE POLICY "Users can create pipeline metas"
77
+ ON public."pipelines_metas"
78
+ FOR INSERT TO authenticated
79
+ WITH CHECK (
80
+ EXISTS (
81
+ SELECT 1 FROM public."pipelines" c
82
+ WHERE c.id = "entityId"
83
+ AND c."userId" = public.get_auth_user_id()
84
+ )
85
+ );
86
+
87
+ CREATE POLICY "Users can update pipeline metas"
88
+ ON public."pipelines_metas"
89
+ FOR UPDATE TO authenticated
90
+ USING (
91
+ EXISTS (
92
+ SELECT 1 FROM public."pipelines" c
93
+ WHERE c.id = "entityId"
94
+ AND c."userId" = public.get_auth_user_id()
95
+ )
96
+ )
97
+ WITH CHECK (
98
+ EXISTS (
99
+ SELECT 1 FROM public."pipelines" c
100
+ WHERE c.id = "entityId"
101
+ AND c."userId" = public.get_auth_user_id()
102
+ )
103
+ );
104
+
105
+ CREATE POLICY "Users can delete pipeline metas"
106
+ ON public."pipelines_metas"
107
+ FOR DELETE TO authenticated
108
+ USING (
109
+ EXISTS (
110
+ SELECT 1 FROM public."pipelines" c
111
+ WHERE c.id = "entityId"
112
+ AND c."userId" = public.get_auth_user_id()
113
+ )
114
+ );
@@ -0,0 +1,91 @@
1
+ -- ============================================================================
2
+ -- Sample data for pipelines table - CRM Theme
3
+ -- Aligned with schema in 001_pipelines_table.sql
4
+ -- Uses users from 999_theme_sample_data.sql
5
+ -- NOTE: IDs are valid UUIDs because opportunities.pipelineId is UUID type
6
+ -- ============================================================================
7
+
8
+ -- Clean existing pipelines data
9
+ DELETE FROM "pipelines" WHERE "teamId" = 'team-crm-company';
10
+
11
+ INSERT INTO "pipelines" (
12
+ id,
13
+ name,
14
+ description,
15
+ type,
16
+ "isDefault",
17
+ "isActive",
18
+ stages,
19
+ "dealRottenDays",
20
+ "userId",
21
+ "teamId",
22
+ "createdAt",
23
+ "updatedAt"
24
+ ) VALUES
25
+ -- Main Sales Pipeline (Default)
26
+ (
27
+ 'cf7f1f00-0001-4000-8000-000000000001',
28
+ 'Main Sales Pipeline',
29
+ 'Primary sales pipeline for all standard opportunities',
30
+ 'sales',
31
+ true,
32
+ true,
33
+ '[
34
+ {"id": "stage_001", "name": "Lead", "order": 1, "probability": 10, "color": "#e3f2fd"},
35
+ {"id": "stage_002", "name": "Qualified", "order": 2, "probability": 25, "color": "#bbdefb"},
36
+ {"id": "stage_003", "name": "Proposal", "order": 3, "probability": 50, "color": "#90caf9"},
37
+ {"id": "stage_004", "name": "Negotiation", "order": 4, "probability": 75, "color": "#64b5f6"},
38
+ {"id": "stage_005", "name": "Closed Won", "order": 5, "probability": 100, "color": "#4caf50"},
39
+ {"id": "stage_006", "name": "Closed Lost", "order": 6, "probability": 0, "color": "#f44336"}
40
+ ]'::jsonb,
41
+ 30,
42
+ 'usr-crm-ceo',
43
+ 'team-crm-company',
44
+ NOW() - INTERVAL '90 days',
45
+ NOW() - INTERVAL '30 days'
46
+ ),
47
+ -- Enterprise Sales Pipeline
48
+ (
49
+ 'cf7f1f00-0002-4000-8000-000000000002',
50
+ 'Enterprise Sales Pipeline',
51
+ 'Specialized pipeline for large enterprise deals with extended sales cycles',
52
+ 'sales',
53
+ false,
54
+ true,
55
+ '[
56
+ {"id": "stage_007", "name": "Discovery", "order": 1, "probability": 5, "color": "#f3e5f5"},
57
+ {"id": "stage_008", "name": "Technical Evaluation", "order": 2, "probability": 15, "color": "#e1bee7"},
58
+ {"id": "stage_009", "name": "Business Case", "order": 3, "probability": 30, "color": "#ce93d8"},
59
+ {"id": "stage_010", "name": "Procurement", "order": 4, "probability": 60, "color": "#ba68c8"},
60
+ {"id": "stage_011", "name": "Legal Review", "order": 5, "probability": 80, "color": "#ab47bc"},
61
+ {"id": "stage_012", "name": "Closed Won", "order": 6, "probability": 100, "color": "#4caf50"},
62
+ {"id": "stage_013", "name": "Closed Lost", "order": 7, "probability": 0, "color": "#f44336"}
63
+ ]'::jsonb,
64
+ 45,
65
+ 'usr-crm-ceo',
66
+ 'team-crm-company',
67
+ NOW() - INTERVAL '60 days',
68
+ NOW() - INTERVAL '15 days'
69
+ ),
70
+ -- Small Business Pipeline
71
+ (
72
+ 'cf7f1f00-0003-4000-8000-000000000003',
73
+ 'Small Business Pipeline',
74
+ 'Pipeline for small business leads with faster sales cycle',
75
+ 'sales',
76
+ false,
77
+ true,
78
+ '[
79
+ {"id": "stage_014", "name": "MQL", "order": 1, "probability": 10, "color": "#fff3e0"},
80
+ {"id": "stage_015", "name": "Nurturing", "order": 2, "probability": 20, "color": "#ffe0b2"},
81
+ {"id": "stage_016", "name": "Engaged", "order": 3, "probability": 40, "color": "#ffcc80"},
82
+ {"id": "stage_017", "name": "Sales Ready", "order": 4, "probability": 70, "color": "#ffb300"},
83
+ {"id": "stage_018", "name": "Converted", "order": 5, "probability": 100, "color": "#4caf50"},
84
+ {"id": "stage_019", "name": "Disqualified", "order": 6, "probability": 0, "color": "#f44336"}
85
+ ]'::jsonb,
86
+ 21,
87
+ 'usr-crm-sales-mgr',
88
+ 'team-crm-company',
89
+ NOW() - INTERVAL '30 days',
90
+ NOW() - INTERVAL '5 days'
91
+ );
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Pipelines Entity Configuration - CRM
3
+ *
4
+ * Sales pipelines with stages.
5
+ * Updated with team support and granular permissions.
6
+ * Only owner can manage pipelines.
7
+ */
8
+
9
+ import { GitBranch } from 'lucide-react'
10
+ import type { EntityConfig } from '@nextsparkjs/core/lib/entities/types'
11
+ import { pipelinesFields } from './pipelines.fields'
12
+
13
+ export const pipelinesEntityConfig: EntityConfig = {
14
+ // ==========================================
15
+ // 1. BASIC IDENTIFICATION
16
+ // ==========================================
17
+ slug: 'pipelines',
18
+ enabled: true,
19
+ names: {
20
+ singular: 'pipeline',
21
+ plural: 'Pipelines'
22
+ },
23
+ icon: GitBranch,
24
+
25
+ // ==========================================
26
+ // 2. ACCESS AND SCOPE CONFIGURATION
27
+ // ==========================================
28
+ access: {
29
+ public: false,
30
+ api: true,
31
+ metadata: false,
32
+ shared: true
33
+ },
34
+
35
+ // ==========================================
36
+ // 3. UI/UX FEATURES
37
+ // ==========================================
38
+ ui: {
39
+ dashboard: {
40
+ showInMenu: false, // Accessed via settings
41
+ showInTopbar: false
42
+ },
43
+ public: {
44
+ hasArchivePage: false,
45
+ hasSinglePage: false
46
+ },
47
+ features: {
48
+ searchable: false,
49
+ sortable: true,
50
+ filterable: false,
51
+ bulkOperations: false,
52
+ importExport: false
53
+ }
54
+ },
55
+
56
+ // ==========================================
57
+ // FIELDS
58
+ // ==========================================
59
+ fields: pipelinesFields,
60
+ }
61
+
62
+ export default pipelinesEntityConfig