@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,241 @@
1
+ /**
2
+ * @startsimpli/funnels - Operator Implementation
3
+ *
4
+ * BRUTALLY GENERIC operator logic that works with ANY data type.
5
+ *
6
+ * Each operator is a pure function: (actual, expected) => boolean
7
+ */
8
+
9
+ import type { Operator } from '../types';
10
+
11
+ /**
12
+ * Apply an operator to compare actual vs expected values
13
+ *
14
+ * @param operator - The comparison operator
15
+ * @param actual - The actual value from entity
16
+ * @param expected - The expected value from rule
17
+ * @returns Whether the comparison passes
18
+ */
19
+ export function applyOperator(
20
+ operator: Operator,
21
+ actual: any,
22
+ expected: any
23
+ ): boolean {
24
+ switch (operator) {
25
+ // ========================================================================
26
+ // Equality
27
+ // ========================================================================
28
+ case 'eq':
29
+ return compareValues(actual, expected, (a, e) => a === e);
30
+
31
+ case 'ne':
32
+ return compareValues(actual, expected, (a, e) => a !== e);
33
+
34
+ // ========================================================================
35
+ // Comparison (numbers, dates, strings)
36
+ // ========================================================================
37
+ case 'gt':
38
+ return compareValues(actual, expected, (a, e) => a > e);
39
+
40
+ case 'lt':
41
+ return compareValues(actual, expected, (a, e) => a < e);
42
+
43
+ case 'gte':
44
+ return compareValues(actual, expected, (a, e) => a >= e);
45
+
46
+ case 'lte':
47
+ return compareValues(actual, expected, (a, e) => a <= e);
48
+
49
+ // ========================================================================
50
+ // String operations (case-insensitive)
51
+ // ========================================================================
52
+ case 'contains':
53
+ if (actual == null) return false;
54
+ if (expected == null) return false;
55
+ return String(actual).toLowerCase().includes(String(expected).toLowerCase());
56
+
57
+ case 'not_contains':
58
+ if (actual == null) return true; // null doesn't contain anything
59
+ if (expected == null) return true;
60
+ return !String(actual).toLowerCase().includes(String(expected).toLowerCase());
61
+
62
+ case 'startswith':
63
+ if (actual == null) return false;
64
+ if (expected == null) return false;
65
+ return String(actual).toLowerCase().startsWith(String(expected).toLowerCase());
66
+
67
+ case 'endswith':
68
+ if (actual == null) return false;
69
+ if (expected == null) return false;
70
+ return String(actual).toLowerCase().endsWith(String(expected).toLowerCase());
71
+
72
+ case 'matches':
73
+ if (actual == null) return false;
74
+ if (expected == null) return false;
75
+ try {
76
+ const regex = new RegExp(String(expected));
77
+ return regex.test(String(actual));
78
+ } catch {
79
+ // Invalid regex pattern
80
+ return false;
81
+ }
82
+
83
+ // ========================================================================
84
+ // Array/Set operations
85
+ // ========================================================================
86
+ case 'in':
87
+ // Value is in array: actual IN expected[]
88
+ if (!Array.isArray(expected)) return false;
89
+ return expected.some(item => compareValues(actual, item, (a, e) => a === e));
90
+
91
+ case 'not_in':
92
+ // Value is not in array: actual NOT IN expected[]
93
+ if (!Array.isArray(expected)) return true;
94
+ return !expected.some(item => compareValues(actual, item, (a, e) => a === e));
95
+
96
+ case 'has_any':
97
+ // Array has any of values: actual[] HAS ANY expected[]
98
+ if (!Array.isArray(actual)) return false;
99
+ if (!Array.isArray(expected)) return false;
100
+ return expected.some(expectedItem =>
101
+ actual.some(actualItem => compareValues(actualItem, expectedItem, (a, e) => a === e))
102
+ );
103
+
104
+ case 'has_all':
105
+ // Array has all values: actual[] HAS ALL expected[]
106
+ if (!Array.isArray(actual)) return false;
107
+ if (!Array.isArray(expected)) return false;
108
+ return expected.every(expectedItem =>
109
+ actual.some(actualItem => compareValues(actualItem, expectedItem, (a, e) => a === e))
110
+ );
111
+
112
+ // ========================================================================
113
+ // Null checks
114
+ // ========================================================================
115
+ case 'isnull':
116
+ return actual === null || actual === undefined;
117
+
118
+ case 'isnotnull':
119
+ return actual !== null && actual !== undefined;
120
+
121
+ // ========================================================================
122
+ // Tag operations (tags are arrays of strings)
123
+ // ========================================================================
124
+ case 'has_tag':
125
+ if (!Array.isArray(actual)) return false;
126
+ if (expected == null) return false;
127
+ // Case-insensitive tag match
128
+ const expectedTag = String(expected).toLowerCase();
129
+ return actual.some(tag => String(tag).toLowerCase() === expectedTag);
130
+
131
+ case 'not_has_tag':
132
+ if (!Array.isArray(actual)) return true; // No tags = doesn't have tag
133
+ if (expected == null) return true;
134
+ const expectedTagNot = String(expected).toLowerCase();
135
+ return !actual.some(tag => String(tag).toLowerCase() === expectedTagNot);
136
+
137
+ // ========================================================================
138
+ // Boolean
139
+ // ========================================================================
140
+ case 'is_true':
141
+ return actual === true;
142
+
143
+ case 'is_false':
144
+ return actual === false;
145
+
146
+ default:
147
+ throw new Error(`Unknown operator: ${operator}`);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Compare values with type normalization
153
+ *
154
+ * Handles:
155
+ * - Numbers: direct comparison
156
+ * - Dates: convert to timestamps
157
+ * - Strings: direct comparison (operators handle case-sensitivity)
158
+ * - null/undefined: handle gracefully
159
+ */
160
+ function compareValues(
161
+ actual: any,
162
+ expected: any,
163
+ compare: (a: any, e: any) => boolean
164
+ ): boolean {
165
+ // Handle null/undefined
166
+ if (actual === null || actual === undefined) {
167
+ return expected === null || expected === undefined;
168
+ }
169
+ if (expected === null || expected === undefined) {
170
+ return false;
171
+ }
172
+
173
+ // Both are dates - convert to timestamps
174
+ if (isDate(actual) && isDate(expected)) {
175
+ const actualTs = toTimestamp(actual);
176
+ const expectedTs = toTimestamp(expected);
177
+ if (actualTs === null || expectedTs === null) return false;
178
+ return compare(actualTs, expectedTs);
179
+ }
180
+
181
+ // One is date - try to parse the other
182
+ if (isDate(actual) || isDate(expected)) {
183
+ const actualTs = toTimestamp(actual);
184
+ const expectedTs = toTimestamp(expected);
185
+ if (actualTs === null || expectedTs === null) return false;
186
+ return compare(actualTs, expectedTs);
187
+ }
188
+
189
+ // Both are numbers
190
+ if (typeof actual === 'number' && typeof expected === 'number') {
191
+ return compare(actual, expected);
192
+ }
193
+
194
+ // Try to convert to numbers if they look numeric
195
+ if (isNumeric(actual) && isNumeric(expected)) {
196
+ return compare(Number(actual), Number(expected));
197
+ }
198
+
199
+ // Default: direct comparison
200
+ return compare(actual, expected);
201
+ }
202
+
203
+ /**
204
+ * Check if value is a Date object or date string
205
+ */
206
+ function isDate(value: any): boolean {
207
+ if (value instanceof Date) return true;
208
+ if (typeof value !== 'string') return false;
209
+
210
+ // Check if string looks like a date
211
+ const datePattern = /^\d{4}-\d{2}-\d{2}|^\d{1,2}\/\d{1,2}\/\d{4}/;
212
+ return datePattern.test(value);
213
+ }
214
+
215
+ /**
216
+ * Convert value to timestamp (milliseconds since epoch)
217
+ * Returns null if conversion fails
218
+ */
219
+ function toTimestamp(value: any): number | null {
220
+ if (value instanceof Date) {
221
+ const ts = value.getTime();
222
+ return isNaN(ts) ? null : ts;
223
+ }
224
+
225
+ if (typeof value === 'string' || typeof value === 'number') {
226
+ const date = new Date(value);
227
+ const ts = date.getTime();
228
+ return isNaN(ts) ? null : ts;
229
+ }
230
+
231
+ return null;
232
+ }
233
+
234
+ /**
235
+ * Check if value is numeric (number or numeric string)
236
+ */
237
+ function isNumeric(value: any): boolean {
238
+ if (typeof value === 'number') return !isNaN(value);
239
+ if (typeof value !== 'string') return false;
240
+ return !isNaN(Number(value)) && value.trim() !== '';
241
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @startsimpli/funnels - React Hooks
3
+ */
4
+
5
+ export { useDebouncedValue } from './useDebouncedValue';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * useDebouncedValue Hook
3
+ *
4
+ * Returns a debounced version of the input value that only updates
5
+ * after the specified delay has elapsed without changes.
6
+ *
7
+ * Use for expensive computations that shouldn't run on every keystroke.
8
+ */
9
+
10
+ import { useEffect, useState } from 'react';
11
+
12
+ export function useDebouncedValue<T>(value: T, delay: number = 300): T {
13
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
14
+
15
+ useEffect(() => {
16
+ // Set timeout to update debounced value after delay
17
+ const handler = setTimeout(() => {
18
+ setDebouncedValue(value);
19
+ }, delay);
20
+
21
+ // Cleanup: cancel timeout if value changes before delay elapses
22
+ return () => {
23
+ clearTimeout(handler);
24
+ };
25
+ }, [value, delay]);
26
+
27
+ return debouncedValue;
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @startsimpli/funnels
3
+ *
4
+ * BRUTALLY GENERIC funnel system for filtering and processing ANY entity type.
5
+ *
6
+ * Works for:
7
+ * - Investors, firms, contacts, organizations
8
+ * - Recipes, ingredients, cooking instructions
9
+ * - Leads, opportunities, tasks, projects
10
+ * - GitHub repos, PRs, issues, users
11
+ * - Products, orders, customers, shipments
12
+ * - Literally ANY data that needs sequential filtering
13
+ *
14
+ * Core concept:
15
+ * 1. Start with entities (any type)
16
+ * 2. Apply sequential filter stages
17
+ * 3. Each stage: keep/exclude/tag based on rules
18
+ * 4. End with filtered subset + accumulated tags/context
19
+ *
20
+ * Example use cases:
21
+ * - Investor qualification funnel (firm stage, check size, geography, etc)
22
+ * - Recipe recommendation funnel (dietary restrictions, cuisine, difficulty)
23
+ * - Lead scoring funnel (company size, industry, engagement level)
24
+ * - Task prioritization funnel (urgency, importance, dependencies)
25
+ *
26
+ * @packageDocumentation
27
+ */
28
+
29
+ // Export all types
30
+ export * from './types';
31
+
32
+ // Export core utilities
33
+ export {
34
+ resolveField,
35
+ setField,
36
+ hasField,
37
+ getFields,
38
+ } from './core/field-resolver';
39
+
40
+ // Export rule evaluator
41
+ export {
42
+ evaluateRule,
43
+ evaluateRuleWithResult,
44
+ evaluateRules,
45
+ evaluateRulesAND,
46
+ evaluateRulesOR,
47
+ evaluateRulesWithResults,
48
+ filterEntities,
49
+ } from './core/evaluator';
50
+
51
+ // Export operators
52
+ export { applyOperator } from './core/operators';
53
+
54
+ // Export funnel engine
55
+ export { FunnelEngine } from './core/engine';
56
+ export type { ExecutionResult } from './core/engine';
57
+
58
+ // Export API client
59
+ export * from './api';
60
+
61
+ // Export store
62
+ export * from './store';
63
+
64
+ // Export hooks
65
+ export { useDebouncedValue } from './hooks';
66
+
67
+ // Export components
68
+ export {
69
+ FunnelPreview,
70
+ PreviewStats,
71
+ StageBreakdown,
72
+ EntityCard,
73
+ LoadingPreview,
74
+ } from './components/FunnelPreview';
75
+ export type {
76
+ FunnelPreviewProps,
77
+ PreviewResult,
78
+ StagePreviewStats,
79
+ } from './components/FunnelPreview';
80
+
81
+ export {
82
+ FunnelCard,
83
+ StatusBadge,
84
+ StageIndicator,
85
+ MatchBar,
86
+ FunnelStats,
87
+ } from './components/FunnelCard';
88
+ export type { FunnelCardProps } from './components/FunnelCard';
89
+
90
+ export {
91
+ FunnelVisualFlow,
92
+ StageNode,
93
+ FlowLegend,
94
+ getCircledNumber,
95
+ } from './components/FunnelVisualFlow';
96
+ export type {
97
+ FunnelVisualFlowProps,
98
+ StageNodeData,
99
+ } from './components/FunnelVisualFlow';
100
+
101
+ export {
102
+ FilterRuleEditor,
103
+ LogicToggle,
104
+ FieldSelector,
105
+ OperatorSelector,
106
+ RuleRow,
107
+ TextValueInput,
108
+ NumberValueInput,
109
+ DateValueInput,
110
+ BooleanValueInput,
111
+ ChoiceValueInput,
112
+ MultiChoiceValueInput,
113
+ OPERATOR_LABELS,
114
+ NULL_VALUE_OPERATORS,
115
+ MULTI_VALUE_OPERATORS,
116
+ } from './components/FilterRuleEditor';
117
+ export type { FilterRuleEditorProps } from './components/FilterRuleEditor';
118
+
119
+ export {
120
+ FunnelStageBuilder,
121
+ StageCard,
122
+ StageForm,
123
+ StageActions,
124
+ TagInput,
125
+ AddStageButton,
126
+ } from './components/FunnelStageBuilder';
127
+ export type {
128
+ FunnelStageBuilderProps,
129
+ StageCardProps,
130
+ StageFormProps,
131
+ StageActionsProps,
132
+ TagInputProps,
133
+ AddStageButtonProps,
134
+ } from './components/FunnelStageBuilder';
135
+
136
+ export {
137
+ FunnelRunHistory,
138
+ RunStatusBadge,
139
+ RunFilters,
140
+ RunRow,
141
+ RunActions,
142
+ RunDetailsModal,
143
+ StageBreakdownList,
144
+ formatDuration,
145
+ formatRelativeTime,
146
+ calculateMatchRate,
147
+ formatNumber,
148
+ formatFullTimestamp,
149
+ } from './components/FunnelRunHistory';
150
+ export type {
151
+ RunFiltersType,
152
+ RunSort,
153
+ Pagination,
154
+ RunAction,
155
+ } from './components/FunnelRunHistory';