@startsimpli/funnels 0.1.4 → 0.1.5

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 (151) hide show
  1. package/package.json +9 -31
  2. package/src/api/README.md +507 -0
  3. package/src/api/adapter.ts +106 -0
  4. package/src/api/client.test.ts +640 -0
  5. package/src/api/client.ts +385 -0
  6. package/src/api/default-adapter.ts +243 -0
  7. package/src/api/index.ts +24 -0
  8. package/src/components/FilterRuleEditor/ARCHITECTURE.md +354 -0
  9. package/src/components/FilterRuleEditor/FieldSelector.tsx +91 -0
  10. package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +462 -0
  11. package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +520 -0
  12. package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +225 -0
  13. package/src/components/FilterRuleEditor/LogicToggle.tsx +64 -0
  14. package/src/components/FilterRuleEditor/OperatorSelector.tsx +75 -0
  15. package/src/components/FilterRuleEditor/README.md +291 -0
  16. package/src/components/FilterRuleEditor/RuleRow.tsx +246 -0
  17. package/src/components/FilterRuleEditor/ValueInputs/BooleanValueInput.tsx +54 -0
  18. package/src/components/FilterRuleEditor/ValueInputs/ChoiceValueInput.tsx +83 -0
  19. package/src/components/FilterRuleEditor/ValueInputs/DateValueInput.tsx +70 -0
  20. package/src/components/FilterRuleEditor/ValueInputs/MultiChoiceValueInput.tsx +132 -0
  21. package/src/components/FilterRuleEditor/ValueInputs/NumberValueInput.tsx +73 -0
  22. package/src/components/FilterRuleEditor/ValueInputs/TextValueInput.tsx +50 -0
  23. package/src/components/FilterRuleEditor/ValueInputs/index.ts +12 -0
  24. package/src/components/FilterRuleEditor/constants.ts +64 -0
  25. package/src/components/FilterRuleEditor/index.ts +14 -0
  26. package/src/components/FunnelCard/DESIGN.md +447 -0
  27. package/src/components/FunnelCard/FunnelCard.stories.tsx +484 -0
  28. package/src/components/FunnelCard/FunnelCard.test.ts +257 -0
  29. package/src/components/FunnelCard/FunnelCard.test.tsx +336 -0
  30. package/src/components/FunnelCard/FunnelCard.tsx +204 -0
  31. package/src/components/FunnelCard/FunnelStats.tsx +68 -0
  32. package/src/components/FunnelCard/IMPLEMENTATION_SUMMARY.md +505 -0
  33. package/src/components/FunnelCard/INSTALLATION.md +304 -0
  34. package/src/components/FunnelCard/MatchBar.tsx +49 -0
  35. package/src/components/FunnelCard/README.md +294 -0
  36. package/src/components/FunnelCard/StageIndicator.tsx +62 -0
  37. package/src/components/FunnelCard/StatusBadge.tsx +52 -0
  38. package/src/components/FunnelCard/index.ts +14 -0
  39. package/src/components/FunnelPreview/EntityCard.tsx +72 -0
  40. package/src/components/FunnelPreview/FunnelPreview.stories.tsx +227 -0
  41. package/src/components/FunnelPreview/FunnelPreview.test.tsx +316 -0
  42. package/src/components/FunnelPreview/FunnelPreview.tsx +249 -0
  43. package/src/components/FunnelPreview/LoadingPreview.tsx +60 -0
  44. package/src/components/FunnelPreview/PreviewStats.tsx +78 -0
  45. package/src/components/FunnelPreview/README.md +337 -0
  46. package/src/components/FunnelPreview/StageBreakdown.tsx +94 -0
  47. package/src/components/FunnelPreview/example.tsx +286 -0
  48. package/src/components/FunnelPreview/index.ts +14 -0
  49. package/src/components/FunnelRunHistory/COMPONENT_SUMMARY.md +246 -0
  50. package/src/components/FunnelRunHistory/FunnelRunHistory.stories.tsx +272 -0
  51. package/src/components/FunnelRunHistory/FunnelRunHistory.test.tsx +323 -0
  52. package/src/components/FunnelRunHistory/FunnelRunHistory.tsx +329 -0
  53. package/src/components/FunnelRunHistory/README.md +325 -0
  54. package/src/components/FunnelRunHistory/RunActions.tsx +168 -0
  55. package/src/components/FunnelRunHistory/RunDetailsModal.tsx +221 -0
  56. package/src/components/FunnelRunHistory/RunFilters.tsx +128 -0
  57. package/src/components/FunnelRunHistory/RunRow.tsx +122 -0
  58. package/src/components/FunnelRunHistory/RunStatusBadge.tsx +75 -0
  59. package/src/components/FunnelRunHistory/StageBreakdownList.tsx +110 -0
  60. package/src/components/FunnelRunHistory/index.ts +51 -0
  61. package/src/components/FunnelRunHistory/types.ts +40 -0
  62. package/src/components/FunnelRunHistory/utils.test.ts +126 -0
  63. package/src/components/FunnelRunHistory/utils.ts +100 -0
  64. package/src/components/FunnelStageBuilder/AddStageButton.tsx +52 -0
  65. package/src/components/FunnelStageBuilder/FunnelStageBuilder.css +413 -0
  66. package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +312 -0
  67. package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +304 -0
  68. package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +321 -0
  69. package/src/components/FunnelStageBuilder/README.md +341 -0
  70. package/src/components/FunnelStageBuilder/StageActions.test.tsx +205 -0
  71. package/src/components/FunnelStageBuilder/StageActions.tsx +126 -0
  72. package/src/components/FunnelStageBuilder/StageCard.tsx +202 -0
  73. package/src/components/FunnelStageBuilder/StageForm.tsx +262 -0
  74. package/src/components/FunnelStageBuilder/TagInput.test.tsx +178 -0
  75. package/src/components/FunnelStageBuilder/TagInput.tsx +129 -0
  76. package/src/components/FunnelStageBuilder/index.ts +21 -0
  77. package/src/components/FunnelVisualFlow/FlowLegend.tsx +77 -0
  78. package/{dist/components/index.css → src/components/FunnelVisualFlow/FunnelVisualFlow.css} +89 -13
  79. package/src/components/FunnelVisualFlow/FunnelVisualFlow.stories.tsx +254 -0
  80. package/src/components/FunnelVisualFlow/FunnelVisualFlow.test.tsx +208 -0
  81. package/src/components/FunnelVisualFlow/FunnelVisualFlow.tsx +229 -0
  82. package/src/components/FunnelVisualFlow/README.md +323 -0
  83. package/src/components/FunnelVisualFlow/StageNode.tsx +188 -0
  84. package/src/components/FunnelVisualFlow/example.tsx +227 -0
  85. package/src/components/FunnelVisualFlow/index.ts +10 -0
  86. package/src/components/index.ts +102 -0
  87. package/src/core/README.md +307 -0
  88. package/src/core/engine.test.ts +1087 -0
  89. package/src/core/engine.ts +329 -0
  90. package/src/core/evaluator.example.ts +353 -0
  91. package/src/core/evaluator.test.ts +639 -0
  92. package/src/core/evaluator.ts +261 -0
  93. package/src/core/field-resolver.example.ts +175 -0
  94. package/src/core/field-resolver.test.ts +541 -0
  95. package/src/core/field-resolver.ts +247 -0
  96. package/src/core/index.ts +34 -0
  97. package/src/core/operators.test.ts +539 -0
  98. package/src/core/operators.ts +241 -0
  99. package/src/hooks/index.ts +5 -0
  100. package/src/hooks/useDebouncedValue.ts +28 -0
  101. package/src/index.ts +155 -0
  102. package/src/store/README.md +342 -0
  103. package/src/store/create-funnel-store.test.ts +686 -0
  104. package/src/store/create-funnel-store.ts +538 -0
  105. package/src/store/index.ts +9 -0
  106. package/src/store/types.ts +294 -0
  107. package/src/stories/CrossDomain.stories.tsx +149 -0
  108. package/src/stories/Welcome.stories.tsx +81 -0
  109. package/src/stories/demo-data/index.ts +3 -0
  110. package/src/stories/demo-data/investors.ts +216 -0
  111. package/src/stories/demo-data/leads.ts +223 -0
  112. package/src/stories/demo-data/recipes.ts +217 -0
  113. package/src/test/setup.ts +5 -0
  114. package/src/types/index.ts +843 -0
  115. package/dist/client-3ESO2NHy.d.ts +0 -310
  116. package/dist/client-CZu03ACp.d.cts +0 -310
  117. package/dist/components/index.cjs +0 -3241
  118. package/dist/components/index.cjs.map +0 -1
  119. package/dist/components/index.css.map +0 -1
  120. package/dist/components/index.d.cts +0 -726
  121. package/dist/components/index.d.ts +0 -726
  122. package/dist/components/index.js +0 -3194
  123. package/dist/components/index.js.map +0 -1
  124. package/dist/core/index.cjs +0 -500
  125. package/dist/core/index.cjs.map +0 -1
  126. package/dist/core/index.d.cts +0 -359
  127. package/dist/core/index.d.ts +0 -359
  128. package/dist/core/index.js +0 -486
  129. package/dist/core/index.js.map +0 -1
  130. package/dist/hooks/index.cjs +0 -20
  131. package/dist/hooks/index.cjs.map +0 -1
  132. package/dist/hooks/index.d.cts +0 -11
  133. package/dist/hooks/index.d.ts +0 -11
  134. package/dist/hooks/index.js +0 -18
  135. package/dist/hooks/index.js.map +0 -1
  136. package/dist/index-BGDEXbuz.d.cts +0 -434
  137. package/dist/index-BGDEXbuz.d.ts +0 -434
  138. package/dist/index.cjs +0 -4499
  139. package/dist/index.cjs.map +0 -1
  140. package/dist/index.css +0 -198
  141. package/dist/index.css.map +0 -1
  142. package/dist/index.d.cts +0 -99
  143. package/dist/index.d.ts +0 -99
  144. package/dist/index.js +0 -4421
  145. package/dist/index.js.map +0 -1
  146. package/dist/store/index.cjs +0 -389
  147. package/dist/store/index.cjs.map +0 -1
  148. package/dist/store/index.d.cts +0 -225
  149. package/dist/store/index.d.ts +0 -225
  150. package/dist/store/index.js +0 -386
  151. package/dist/store/index.js.map +0 -1
@@ -0,0 +1,337 @@
1
+ # FunnelPreview Component
2
+
3
+ Real-time preview of funnel execution on sample entities.
4
+
5
+ ## Purpose
6
+
7
+ Shows users how their funnel configuration affects entities BEFORE running the full funnel. Updates automatically as they edit filter rules, providing instant feedback on match rates and stage-by-stage results.
8
+
9
+ ## Usage
10
+
11
+ ### Basic Example
12
+
13
+ ```tsx
14
+ import { FunnelPreview } from '@simpli/funnels';
15
+
16
+ function MyComponent() {
17
+ const funnel = {
18
+ id: 'my-funnel',
19
+ name: 'Investor Qualification',
20
+ status: 'active',
21
+ input_type: 'contacts',
22
+ stages: [
23
+ {
24
+ id: 'stage-1',
25
+ order: 0,
26
+ name: 'Series A or Later',
27
+ filter_logic: 'AND',
28
+ rules: [
29
+ {
30
+ field_path: 'firm.stage',
31
+ operator: 'in',
32
+ value: ['Series A', 'Series B', 'Series C'],
33
+ },
34
+ ],
35
+ match_action: 'continue',
36
+ no_match_action: 'exclude',
37
+ },
38
+ ],
39
+ created_at: new Date().toISOString(),
40
+ updated_at: new Date().toISOString(),
41
+ };
42
+
43
+ const sampleInvestors = [
44
+ { name: 'John Doe', firm: { name: 'Acme VC', stage: 'Series A' }, score: 85 },
45
+ { name: 'Jane Smith', firm: { name: 'TechCo', stage: 'Seed' }, score: 92 },
46
+ // ... more investors
47
+ ];
48
+
49
+ return (
50
+ <FunnelPreview
51
+ funnel={funnel}
52
+ sampleEntities={sampleInvestors}
53
+ />
54
+ );
55
+ }
56
+ ```
57
+
58
+ ### With Custom Entity Renderer
59
+
60
+ ```tsx
61
+ <FunnelPreview
62
+ funnel={funnel}
63
+ sampleEntities={investors}
64
+ renderEntity={(investor) => (
65
+ <div>
66
+ <div className="font-medium">{investor.name}</div>
67
+ <div className="text-sm text-gray-600">
68
+ {investor.firm.name} · {investor.firm.stage} · Score: {investor.score}
69
+ </div>
70
+ </div>
71
+ )}
72
+ />
73
+ ```
74
+
75
+ ### With Preview Callback
76
+
77
+ ```tsx
78
+ <FunnelPreview
79
+ funnel={funnel}
80
+ sampleEntities={entities}
81
+ onPreview={(result) => {
82
+ console.log('Matched:', result.totalMatched);
83
+ console.log('Excluded:', result.totalExcluded);
84
+ console.log('Match %:', result.matchPercentage);
85
+ }}
86
+ />
87
+ ```
88
+
89
+ ### Customize Preview Limit
90
+
91
+ ```tsx
92
+ <FunnelPreview
93
+ funnel={funnel}
94
+ sampleEntities={entities}
95
+ maxPreviewEntities={20} // Show up to 20 entities (default: 10)
96
+ />
97
+ ```
98
+
99
+ ## Props
100
+
101
+ | Prop | Type | Required | Default | Description |
102
+ |------|------|----------|---------|-------------|
103
+ | `funnel` | `Funnel` | Yes | - | The funnel to preview |
104
+ | `sampleEntities` | `any[]` | Yes | - | Sample entities to process |
105
+ | `onPreview` | `(result: PreviewResult) => void` | No | - | Callback when preview updates |
106
+ | `renderEntity` | `(entity: any) => ReactNode` | No | Default renderer | Custom entity display |
107
+ | `maxPreviewEntities` | `number` | No | `10` | Max entities to show |
108
+ | `className` | `string` | No | `''` | Additional CSS classes |
109
+
110
+ ## Preview Result
111
+
112
+ The `onPreview` callback receives a `PreviewResult`:
113
+
114
+ ```typescript
115
+ interface PreviewResult {
116
+ totalMatched: number;
117
+ totalExcluded: number;
118
+ matchPercentage: number;
119
+ previewEntities: any[];
120
+ stageStats: Record<string, StagePreviewStats>;
121
+ }
122
+
123
+ interface StagePreviewStats {
124
+ stage_id: string;
125
+ stage_name: string;
126
+ input_count: number;
127
+ excluded_count: number;
128
+ remaining_count: number;
129
+ }
130
+ ```
131
+
132
+ ## Features
133
+
134
+ ### Debounced Updates
135
+
136
+ Preview automatically re-computes when funnel or entities change, with a 300ms debounce to prevent excessive computation during editing.
137
+
138
+ ### Loading States
139
+
140
+ Shows skeleton UI on initial load and a subtle overlay during updates.
141
+
142
+ ### Empty States
143
+
144
+ Displays helpful message when no entities match the funnel.
145
+
146
+ ### Stage Breakdown
147
+
148
+ Visual flow showing how many entities are filtered at each stage.
149
+
150
+ ### Match Statistics
151
+
152
+ Progress bar and counts showing overall funnel effectiveness.
153
+
154
+ ## Subcomponents
155
+
156
+ All subcomponents are exported and can be used independently:
157
+
158
+ ### PreviewStats
159
+
160
+ Shows matched vs excluded counts with progress bar.
161
+
162
+ ```tsx
163
+ import { PreviewStats } from '@simpli/funnels';
164
+
165
+ <PreviewStats
166
+ totalMatched={235}
167
+ totalExcluded={765}
168
+ matchPercentage={23.5}
169
+ />
170
+ ```
171
+
172
+ ### StageBreakdown
173
+
174
+ Shows per-stage statistics in funnel flow.
175
+
176
+ ```tsx
177
+ import { StageBreakdown } from '@simpli/funnels';
178
+
179
+ <StageBreakdown
180
+ stageStats={stageStats}
181
+ stages={funnel.stages}
182
+ />
183
+ ```
184
+
185
+ ### EntityCard
186
+
187
+ Displays a single entity with custom renderer.
188
+
189
+ ```tsx
190
+ import { EntityCard } from '@simpli/funnels';
191
+
192
+ <EntityCard
193
+ entity={investor}
194
+ renderEntity={(inv) => (
195
+ <div>{inv.name} - {inv.firm.name}</div>
196
+ )}
197
+ />
198
+ ```
199
+
200
+ ### LoadingPreview
201
+
202
+ Skeleton loading state.
203
+
204
+ ```tsx
205
+ import { LoadingPreview } from '@simpli/funnels';
206
+
207
+ {isLoading && <LoadingPreview />}
208
+ ```
209
+
210
+ ## Examples
211
+
212
+ ### Recipe Funnel
213
+
214
+ ```tsx
215
+ const recipeFunnel = {
216
+ id: 'recipe-filter',
217
+ name: 'Recipe Recommendations',
218
+ status: 'active',
219
+ input_type: 'any',
220
+ stages: [
221
+ {
222
+ id: 'dietary',
223
+ order: 0,
224
+ name: 'Dietary Restrictions',
225
+ filter_logic: 'AND',
226
+ rules: [
227
+ { field_path: 'tags', operator: 'has_tag', value: 'vegetarian' },
228
+ ],
229
+ match_action: 'continue',
230
+ no_match_action: 'exclude',
231
+ },
232
+ ],
233
+ created_at: new Date().toISOString(),
234
+ updated_at: new Date().toISOString(),
235
+ };
236
+
237
+ <FunnelPreview
238
+ funnel={recipeFunnel}
239
+ sampleEntities={recipes}
240
+ renderEntity={(recipe) => (
241
+ <div>
242
+ <div className="font-medium">{recipe.name}</div>
243
+ <div className="text-sm text-gray-600">
244
+ {recipe.cuisine} · {recipe.difficulty} · {recipe.cookTime}
245
+ </div>
246
+ </div>
247
+ )}
248
+ />
249
+ ```
250
+
251
+ ### Lead Scoring Funnel
252
+
253
+ ```tsx
254
+ const leadFunnel = {
255
+ id: 'lead-scoring',
256
+ name: 'Qualified Leads',
257
+ status: 'active',
258
+ input_type: 'any',
259
+ stages: [
260
+ {
261
+ id: 'company-size',
262
+ order: 0,
263
+ name: 'Company Size',
264
+ filter_logic: 'AND',
265
+ rules: [
266
+ { field_path: 'company.employees', operator: 'gte', value: 50 },
267
+ ],
268
+ match_action: 'continue',
269
+ no_match_action: 'exclude',
270
+ },
271
+ {
272
+ id: 'engagement',
273
+ order: 1,
274
+ name: 'Engagement Score',
275
+ filter_logic: 'AND',
276
+ rules: [
277
+ { field_path: 'engagement_score', operator: 'gte', value: 7 },
278
+ ],
279
+ match_action: 'output',
280
+ no_match_action: 'exclude',
281
+ },
282
+ ],
283
+ created_at: new Date().toISOString(),
284
+ updated_at: new Date().toISOString(),
285
+ };
286
+
287
+ <FunnelPreview
288
+ funnel={leadFunnel}
289
+ sampleEntities={leads}
290
+ renderEntity={(lead) => (
291
+ <div>
292
+ <div className="font-medium">{lead.name}</div>
293
+ <div className="text-sm text-gray-600">
294
+ {lead.company.name} · {lead.company.employees} employees · Score: {lead.engagement_score}
295
+ </div>
296
+ </div>
297
+ )}
298
+ />
299
+ ```
300
+
301
+ ## Accessibility
302
+
303
+ - ARIA progressbar role on stats bar
304
+ - ARIA live region announces updates
305
+ - Loading states communicated to screen readers
306
+ - Keyboard navigation through entity cards
307
+ - Color is supplementary (text labels provide meaning)
308
+ - Sufficient color contrast (WCAG AA)
309
+
310
+ ## Performance
311
+
312
+ - Debounced updates (300ms) prevent excessive re-computation
313
+ - Efficient execution via FunnelEngine
314
+ - Limits preview entities (default 10) for fast rendering
315
+ - Skeleton UI reduces perceived loading time
316
+
317
+ ## Design System
318
+
319
+ Uses Tailwind CSS classes matching the Simpli design system:
320
+
321
+ - Colors: `blue-*` (input), `green-*` (matched), `red-*` (excluded), `gray-*` (neutral)
322
+ - Spacing: `gap-2`, `gap-3`, `mb-3`, `mb-6`
323
+ - Typography: `text-sm`, `text-lg`, `font-medium`, `font-semibold`
324
+ - Borders: `rounded-lg`, `border-gray-200`
325
+ - Shadows: `shadow-sm`
326
+
327
+ ## BRUTALLY GENERIC
328
+
329
+ Works with ANY entity type:
330
+
331
+ - Investors, firms, contacts, organizations
332
+ - Recipes, ingredients, cooking instructions
333
+ - Leads, opportunities, tasks, projects
334
+ - GitHub repos, PRs, issues, users
335
+ - Products, orders, customers, shipments
336
+
337
+ No domain-specific logic. All filtering based on generic field paths and operators.
@@ -0,0 +1,94 @@
1
+ /**
2
+ * StageBreakdown Component
3
+ *
4
+ * Shows per-stage statistics in a funnel flow visualization.
5
+ *
6
+ * Design Rationale:
7
+ * - Sequential list shows funnel flow from top to bottom
8
+ * - Stage numbers (①②③) provide clear ordering
9
+ * - Exclusion count (-N) shows entities removed at each stage
10
+ * - Remaining count shows entities continuing to next stage
11
+ * - Color coding: gray for excluded, green for final matched
12
+ *
13
+ * Accessibility:
14
+ * - Semantic list structure
15
+ * - Clear text labels for all metrics
16
+ * - Color is supplementary to text
17
+ */
18
+
19
+ import type { StageStats } from '../../types';
20
+
21
+ export interface StagePreviewStats {
22
+ stage_id: string;
23
+ stage_name: string;
24
+ input_count: number;
25
+ excluded_count: number;
26
+ remaining_count: number;
27
+ }
28
+
29
+ interface StageBreakdownProps {
30
+ stageStats: Record<string, StagePreviewStats>;
31
+ stages: Array<{ id: string; name: string; order: number }>;
32
+ className?: string;
33
+ }
34
+
35
+ export function StageBreakdown({
36
+ stageStats,
37
+ stages,
38
+ className = '',
39
+ }: StageBreakdownProps) {
40
+ // Sort stages by order
41
+ const sortedStages = [...stages].sort((a, b) => a.order - b.order);
42
+
43
+ return (
44
+ <div className={className}>
45
+ <h3 className="text-sm font-semibold text-gray-700 mb-3">
46
+ Stage Breakdown
47
+ </h3>
48
+
49
+ <ol className="space-y-2">
50
+ {sortedStages.map((stage, index) => {
51
+ const stats = stageStats[stage.id];
52
+ if (!stats) return null;
53
+
54
+ const isLast = index === sortedStages.length - 1;
55
+ const excludedCount = stats.excluded_count;
56
+ const remainingCount = stats.remaining_count;
57
+
58
+ return (
59
+ <li
60
+ key={stage.id}
61
+ className="flex items-center justify-between py-2 px-3 bg-gray-50 rounded-lg"
62
+ >
63
+ {/* Stage info */}
64
+ <div className="flex items-center gap-2 flex-1 min-w-0">
65
+ <span className="flex-shrink-0 w-6 h-6 flex items-center justify-center bg-blue-100 text-blue-700 rounded-full text-xs font-bold">
66
+ {index + 1}
67
+ </span>
68
+ <span className="text-sm font-medium text-gray-900 truncate">
69
+ {stage.name}
70
+ </span>
71
+ </div>
72
+
73
+ {/* Stats */}
74
+ <div className="flex items-center gap-3 text-sm">
75
+ {excludedCount > 0 && (
76
+ <span className="text-red-600 font-medium">
77
+ -{excludedCount.toLocaleString()}
78
+ </span>
79
+ )}
80
+ <span
81
+ className={`font-semibold ${
82
+ isLast ? 'text-green-600' : 'text-gray-700'
83
+ }`}
84
+ >
85
+ {remainingCount.toLocaleString()} {isLast ? 'final' : 'left'}
86
+ </span>
87
+ </div>
88
+ </li>
89
+ );
90
+ })}
91
+ </ol>
92
+ </div>
93
+ );
94
+ }
@@ -0,0 +1,286 @@
1
+ /**
2
+ * FunnelPreview Example Usage
3
+ *
4
+ * This file shows how to use the FunnelPreview component
5
+ * in different scenarios.
6
+ */
7
+
8
+ import { FunnelPreview } from './FunnelPreview';
9
+ import type { Funnel } from '../../types';
10
+
11
+ // ============================================================================
12
+ // Example 1: Investor Qualification Funnel
13
+ // ============================================================================
14
+
15
+ const investorFunnel: Funnel = {
16
+ id: 'investor-qualification',
17
+ name: 'Investor Qualification',
18
+ description: 'Filter investors by firm stage and check size',
19
+ status: 'active',
20
+ input_type: 'contacts',
21
+ stages: [
22
+ {
23
+ id: 'stage-1',
24
+ order: 0,
25
+ name: 'Series A or Later',
26
+ filter_logic: 'OR',
27
+ rules: [
28
+ { field_path: 'firm.stage', operator: 'eq', value: 'Series A' },
29
+ { field_path: 'firm.stage', operator: 'eq', value: 'Series B' },
30
+ { field_path: 'firm.stage', operator: 'eq', value: 'Series C' },
31
+ ],
32
+ match_action: 'continue',
33
+ no_match_action: 'exclude',
34
+ },
35
+ {
36
+ id: 'stage-2',
37
+ order: 1,
38
+ name: 'High ICP Score',
39
+ filter_logic: 'AND',
40
+ rules: [{ field_path: 'icp_score', operator: 'gte', value: 80 }],
41
+ match_action: 'output',
42
+ no_match_action: 'exclude',
43
+ },
44
+ ],
45
+ created_at: new Date().toISOString(),
46
+ updated_at: new Date().toISOString(),
47
+ };
48
+
49
+ const sampleInvestors = [
50
+ {
51
+ name: 'John Doe',
52
+ firm: { name: 'Acme VC', stage: 'Series A' },
53
+ icp_score: 85,
54
+ email: 'john@acme.vc',
55
+ },
56
+ {
57
+ name: 'Jane Smith',
58
+ firm: { name: 'TechCo Ventures', stage: 'Seed' },
59
+ icp_score: 92,
60
+ email: 'jane@techco.com',
61
+ },
62
+ {
63
+ name: 'Bob Wilson',
64
+ firm: { name: 'Growth Capital', stage: 'Series B' },
65
+ icp_score: 78,
66
+ email: 'bob@growth.capital',
67
+ },
68
+ {
69
+ name: 'Alice Johnson',
70
+ firm: { name: 'Early Stage Fund', stage: 'Series A' },
71
+ icp_score: 88,
72
+ email: 'alice@earlystage.fund',
73
+ },
74
+ ];
75
+
76
+ export function InvestorQualificationExample() {
77
+ return (
78
+ <div className="max-w-2xl mx-auto p-6">
79
+ <h1 className="text-2xl font-bold mb-6">Investor Qualification Preview</h1>
80
+
81
+ <FunnelPreview
82
+ funnel={investorFunnel}
83
+ sampleEntities={sampleInvestors}
84
+ renderEntity={(investor) => (
85
+ <div>
86
+ <div className="font-medium text-gray-900">{investor.name}</div>
87
+ <div className="text-sm text-gray-600 mt-1">
88
+ {investor.firm.name} · {investor.firm.stage} · ICP Score:{' '}
89
+ {investor.icp_score}
90
+ </div>
91
+ <div className="text-xs text-gray-500 mt-0.5">{investor.email}</div>
92
+ </div>
93
+ )}
94
+ onPreview={(result) => {
95
+ console.log('Preview updated:', {
96
+ matched: result.totalMatched,
97
+ excluded: result.totalExcluded,
98
+ percentage: result.matchPercentage,
99
+ });
100
+ }}
101
+ />
102
+ </div>
103
+ );
104
+ }
105
+
106
+ // ============================================================================
107
+ // Example 2: Recipe Filtering
108
+ // ============================================================================
109
+
110
+ const recipeFunnel: Funnel = {
111
+ id: 'recipe-filter',
112
+ name: 'Recipe Recommendations',
113
+ description: 'Filter recipes by dietary restrictions and difficulty',
114
+ status: 'active',
115
+ input_type: 'any',
116
+ stages: [
117
+ {
118
+ id: 'dietary',
119
+ order: 0,
120
+ name: 'Vegetarian',
121
+ filter_logic: 'AND',
122
+ rules: [{ field_path: 'tags', operator: 'has_tag', value: 'vegetarian' }],
123
+ match_action: 'continue',
124
+ no_match_action: 'exclude',
125
+ },
126
+ {
127
+ id: 'difficulty',
128
+ order: 1,
129
+ name: 'Easy to Moderate',
130
+ filter_logic: 'OR',
131
+ rules: [
132
+ { field_path: 'difficulty', operator: 'eq', value: 'easy' },
133
+ { field_path: 'difficulty', operator: 'eq', value: 'moderate' },
134
+ ],
135
+ match_action: 'output',
136
+ no_match_action: 'exclude',
137
+ },
138
+ ],
139
+ created_at: new Date().toISOString(),
140
+ updated_at: new Date().toISOString(),
141
+ };
142
+
143
+ const sampleRecipes = [
144
+ {
145
+ name: 'Vegetable Stir Fry',
146
+ cuisine: 'Asian',
147
+ difficulty: 'easy',
148
+ cookTime: '20 min',
149
+ tags: ['vegetarian', 'quick', 'healthy'],
150
+ },
151
+ {
152
+ name: 'Beef Tacos',
153
+ cuisine: 'Mexican',
154
+ difficulty: 'easy',
155
+ cookTime: '30 min',
156
+ tags: ['quick', 'family-friendly'],
157
+ },
158
+ {
159
+ name: 'Veggie Curry',
160
+ cuisine: 'Indian',
161
+ difficulty: 'moderate',
162
+ cookTime: '45 min',
163
+ tags: ['vegetarian', 'spicy'],
164
+ },
165
+ ];
166
+
167
+ export function RecipeFilterExample() {
168
+ return (
169
+ <div className="max-w-2xl mx-auto p-6">
170
+ <h1 className="text-2xl font-bold mb-6">Recipe Filter Preview</h1>
171
+
172
+ <FunnelPreview
173
+ funnel={recipeFunnel}
174
+ sampleEntities={sampleRecipes}
175
+ renderEntity={(recipe) => (
176
+ <div>
177
+ <div className="font-medium text-gray-900">{recipe.name}</div>
178
+ <div className="text-sm text-gray-600 mt-1">
179
+ {recipe.cuisine} · {recipe.difficulty} · {recipe.cookTime}
180
+ </div>
181
+ <div className="text-xs text-gray-500 mt-1">
182
+ {recipe.tags.join(', ')}
183
+ </div>
184
+ </div>
185
+ )}
186
+ maxPreviewEntities={5}
187
+ />
188
+ </div>
189
+ );
190
+ }
191
+
192
+ // ============================================================================
193
+ // Example 3: Lead Scoring
194
+ // ============================================================================
195
+
196
+ const leadFunnel: Funnel = {
197
+ id: 'lead-scoring',
198
+ name: 'Qualified Leads',
199
+ description: 'Score and filter leads based on company size and engagement',
200
+ status: 'active',
201
+ input_type: 'any',
202
+ stages: [
203
+ {
204
+ id: 'company-size',
205
+ order: 0,
206
+ name: 'Mid-Market and Above',
207
+ filter_logic: 'AND',
208
+ rules: [{ field_path: 'company.employees', operator: 'gte', value: 50 }],
209
+ match_action: 'continue',
210
+ no_match_action: 'exclude',
211
+ },
212
+ {
213
+ id: 'engagement',
214
+ order: 1,
215
+ name: 'High Engagement',
216
+ filter_logic: 'AND',
217
+ rules: [{ field_path: 'engagement_score', operator: 'gte', value: 7 }],
218
+ match_action: 'tag_continue',
219
+ no_match_action: 'continue',
220
+ match_tags: ['high-engagement'],
221
+ },
222
+ {
223
+ id: 'industry',
224
+ order: 2,
225
+ name: 'Target Industries',
226
+ filter_logic: 'OR',
227
+ rules: [
228
+ { field_path: 'company.industry', operator: 'eq', value: 'Technology' },
229
+ { field_path: 'company.industry', operator: 'eq', value: 'SaaS' },
230
+ { field_path: 'company.industry', operator: 'eq', value: 'FinTech' },
231
+ ],
232
+ match_action: 'output',
233
+ no_match_action: 'exclude',
234
+ },
235
+ ],
236
+ created_at: new Date().toISOString(),
237
+ updated_at: new Date().toISOString(),
238
+ };
239
+
240
+ const sampleLeads = [
241
+ {
242
+ name: 'Sarah Chen',
243
+ title: 'VP of Engineering',
244
+ company: { name: 'TechCorp', industry: 'Technology', employees: 150 },
245
+ engagement_score: 8,
246
+ },
247
+ {
248
+ name: 'Mike Brown',
249
+ title: 'CTO',
250
+ company: { name: 'SmallCo', industry: 'Retail', employees: 20 },
251
+ engagement_score: 9,
252
+ },
253
+ {
254
+ name: 'Lisa Wang',
255
+ title: 'Head of Product',
256
+ company: { name: 'FinanceApp', industry: 'FinTech', employees: 200 },
257
+ engagement_score: 6,
258
+ },
259
+ ];
260
+
261
+ export function LeadScoringExample() {
262
+ return (
263
+ <div className="max-w-2xl mx-auto p-6">
264
+ <h1 className="text-2xl font-bold mb-6">Lead Scoring Preview</h1>
265
+
266
+ <FunnelPreview
267
+ funnel={leadFunnel}
268
+ sampleEntities={sampleLeads}
269
+ renderEntity={(lead) => (
270
+ <div>
271
+ <div className="font-medium text-gray-900">
272
+ {lead.name} - {lead.title}
273
+ </div>
274
+ <div className="text-sm text-gray-600 mt-1">
275
+ {lead.company.name} · {lead.company.industry} ·{' '}
276
+ {lead.company.employees} employees
277
+ </div>
278
+ <div className="text-xs text-gray-500 mt-0.5">
279
+ Engagement Score: {lead.engagement_score}/10
280
+ </div>
281
+ </div>
282
+ )}
283
+ />
284
+ </div>
285
+ );
286
+ }