@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.
- package/package.json +9 -31
- package/src/api/README.md +507 -0
- package/src/api/adapter.ts +106 -0
- package/src/api/client.test.ts +640 -0
- package/src/api/client.ts +385 -0
- package/src/api/default-adapter.ts +243 -0
- package/src/api/index.ts +24 -0
- package/src/components/FilterRuleEditor/ARCHITECTURE.md +354 -0
- package/src/components/FilterRuleEditor/FieldSelector.tsx +91 -0
- package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +462 -0
- package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +520 -0
- package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +225 -0
- package/src/components/FilterRuleEditor/LogicToggle.tsx +64 -0
- package/src/components/FilterRuleEditor/OperatorSelector.tsx +75 -0
- package/src/components/FilterRuleEditor/README.md +291 -0
- package/src/components/FilterRuleEditor/RuleRow.tsx +246 -0
- package/src/components/FilterRuleEditor/ValueInputs/BooleanValueInput.tsx +54 -0
- package/src/components/FilterRuleEditor/ValueInputs/ChoiceValueInput.tsx +83 -0
- package/src/components/FilterRuleEditor/ValueInputs/DateValueInput.tsx +70 -0
- package/src/components/FilterRuleEditor/ValueInputs/MultiChoiceValueInput.tsx +132 -0
- package/src/components/FilterRuleEditor/ValueInputs/NumberValueInput.tsx +73 -0
- package/src/components/FilterRuleEditor/ValueInputs/TextValueInput.tsx +50 -0
- package/src/components/FilterRuleEditor/ValueInputs/index.ts +12 -0
- package/src/components/FilterRuleEditor/constants.ts +64 -0
- package/src/components/FilterRuleEditor/index.ts +14 -0
- package/src/components/FunnelCard/DESIGN.md +447 -0
- package/src/components/FunnelCard/FunnelCard.stories.tsx +484 -0
- package/src/components/FunnelCard/FunnelCard.test.ts +257 -0
- package/src/components/FunnelCard/FunnelCard.test.tsx +336 -0
- package/src/components/FunnelCard/FunnelCard.tsx +204 -0
- package/src/components/FunnelCard/FunnelStats.tsx +68 -0
- package/src/components/FunnelCard/IMPLEMENTATION_SUMMARY.md +505 -0
- package/src/components/FunnelCard/INSTALLATION.md +304 -0
- package/src/components/FunnelCard/MatchBar.tsx +49 -0
- package/src/components/FunnelCard/README.md +294 -0
- package/src/components/FunnelCard/StageIndicator.tsx +62 -0
- package/src/components/FunnelCard/StatusBadge.tsx +52 -0
- package/src/components/FunnelCard/index.ts +14 -0
- package/src/components/FunnelPreview/EntityCard.tsx +72 -0
- package/src/components/FunnelPreview/FunnelPreview.stories.tsx +227 -0
- package/src/components/FunnelPreview/FunnelPreview.test.tsx +316 -0
- package/src/components/FunnelPreview/FunnelPreview.tsx +249 -0
- package/src/components/FunnelPreview/LoadingPreview.tsx +60 -0
- package/src/components/FunnelPreview/PreviewStats.tsx +78 -0
- package/src/components/FunnelPreview/README.md +337 -0
- package/src/components/FunnelPreview/StageBreakdown.tsx +94 -0
- package/src/components/FunnelPreview/example.tsx +286 -0
- package/src/components/FunnelPreview/index.ts +14 -0
- package/src/components/FunnelRunHistory/COMPONENT_SUMMARY.md +246 -0
- package/src/components/FunnelRunHistory/FunnelRunHistory.stories.tsx +272 -0
- package/src/components/FunnelRunHistory/FunnelRunHistory.test.tsx +323 -0
- package/src/components/FunnelRunHistory/FunnelRunHistory.tsx +329 -0
- package/src/components/FunnelRunHistory/README.md +325 -0
- package/src/components/FunnelRunHistory/RunActions.tsx +168 -0
- package/src/components/FunnelRunHistory/RunDetailsModal.tsx +221 -0
- package/src/components/FunnelRunHistory/RunFilters.tsx +128 -0
- package/src/components/FunnelRunHistory/RunRow.tsx +122 -0
- package/src/components/FunnelRunHistory/RunStatusBadge.tsx +75 -0
- package/src/components/FunnelRunHistory/StageBreakdownList.tsx +110 -0
- package/src/components/FunnelRunHistory/index.ts +51 -0
- package/src/components/FunnelRunHistory/types.ts +40 -0
- package/src/components/FunnelRunHistory/utils.test.ts +126 -0
- package/src/components/FunnelRunHistory/utils.ts +100 -0
- package/src/components/FunnelStageBuilder/AddStageButton.tsx +52 -0
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.css +413 -0
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +312 -0
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +304 -0
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +321 -0
- package/src/components/FunnelStageBuilder/README.md +341 -0
- package/src/components/FunnelStageBuilder/StageActions.test.tsx +205 -0
- package/src/components/FunnelStageBuilder/StageActions.tsx +126 -0
- package/src/components/FunnelStageBuilder/StageCard.tsx +202 -0
- package/src/components/FunnelStageBuilder/StageForm.tsx +262 -0
- package/src/components/FunnelStageBuilder/TagInput.test.tsx +178 -0
- package/src/components/FunnelStageBuilder/TagInput.tsx +129 -0
- package/src/components/FunnelStageBuilder/index.ts +21 -0
- package/src/components/FunnelVisualFlow/FlowLegend.tsx +77 -0
- package/{dist/components/index.css → src/components/FunnelVisualFlow/FunnelVisualFlow.css} +89 -13
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.stories.tsx +254 -0
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.test.tsx +208 -0
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.tsx +229 -0
- package/src/components/FunnelVisualFlow/README.md +323 -0
- package/src/components/FunnelVisualFlow/StageNode.tsx +188 -0
- package/src/components/FunnelVisualFlow/example.tsx +227 -0
- package/src/components/FunnelVisualFlow/index.ts +10 -0
- package/src/components/index.ts +102 -0
- package/src/core/README.md +307 -0
- package/src/core/engine.test.ts +1087 -0
- package/src/core/engine.ts +329 -0
- package/src/core/evaluator.example.ts +353 -0
- package/src/core/evaluator.test.ts +639 -0
- package/src/core/evaluator.ts +261 -0
- package/src/core/field-resolver.example.ts +175 -0
- package/src/core/field-resolver.test.ts +541 -0
- package/src/core/field-resolver.ts +247 -0
- package/src/core/index.ts +34 -0
- package/src/core/operators.test.ts +539 -0
- package/src/core/operators.ts +241 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useDebouncedValue.ts +28 -0
- package/src/index.ts +155 -0
- package/src/store/README.md +342 -0
- package/src/store/create-funnel-store.test.ts +686 -0
- package/src/store/create-funnel-store.ts +538 -0
- package/src/store/index.ts +9 -0
- package/src/store/types.ts +294 -0
- package/src/stories/CrossDomain.stories.tsx +149 -0
- package/src/stories/Welcome.stories.tsx +81 -0
- package/src/stories/demo-data/index.ts +3 -0
- package/src/stories/demo-data/investors.ts +216 -0
- package/src/stories/demo-data/leads.ts +223 -0
- package/src/stories/demo-data/recipes.ts +217 -0
- package/src/test/setup.ts +5 -0
- package/src/types/index.ts +843 -0
- package/dist/client-3ESO2NHy.d.ts +0 -310
- package/dist/client-CZu03ACp.d.cts +0 -310
- package/dist/components/index.cjs +0 -3241
- package/dist/components/index.cjs.map +0 -1
- package/dist/components/index.css.map +0 -1
- package/dist/components/index.d.cts +0 -726
- package/dist/components/index.d.ts +0 -726
- package/dist/components/index.js +0 -3194
- package/dist/components/index.js.map +0 -1
- package/dist/core/index.cjs +0 -500
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -359
- package/dist/core/index.d.ts +0 -359
- package/dist/core/index.js +0 -486
- package/dist/core/index.js.map +0 -1
- package/dist/hooks/index.cjs +0 -20
- package/dist/hooks/index.cjs.map +0 -1
- package/dist/hooks/index.d.cts +0 -11
- package/dist/hooks/index.d.ts +0 -11
- package/dist/hooks/index.js +0 -18
- package/dist/hooks/index.js.map +0 -1
- package/dist/index-BGDEXbuz.d.cts +0 -434
- package/dist/index-BGDEXbuz.d.ts +0 -434
- package/dist/index.cjs +0 -4499
- package/dist/index.cjs.map +0 -1
- package/dist/index.css +0 -198
- package/dist/index.css.map +0 -1
- package/dist/index.d.cts +0 -99
- package/dist/index.d.ts +0 -99
- package/dist/index.js +0 -4421
- package/dist/index.js.map +0 -1
- package/dist/store/index.cjs +0 -389
- package/dist/store/index.cjs.map +0 -1
- package/dist/store/index.d.cts +0 -225
- package/dist/store/index.d.ts +0 -225
- package/dist/store/index.js +0 -386
- package/dist/store/index.js.map +0 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# FunnelCard Installation Guide
|
|
2
|
+
|
|
3
|
+
## Quick Start
|
|
4
|
+
|
|
5
|
+
The FunnelCard component is already built and ready to use. It just needs React dependencies to render.
|
|
6
|
+
|
|
7
|
+
## Install React Dependencies
|
|
8
|
+
|
|
9
|
+
Since this package currently only has TypeScript/Zustand dependencies, you'll need to add React:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cd /Users/qosha/Repos/small-bizs/startsimpli/start-simpli-api/packages/funnels
|
|
13
|
+
|
|
14
|
+
# Add React (peer dependency)
|
|
15
|
+
npm install --save-peer react react-dom
|
|
16
|
+
|
|
17
|
+
# Add React types (dev dependency)
|
|
18
|
+
npm install --save-dev @types/react @types/react-dom
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Optional: Testing Dependencies
|
|
22
|
+
|
|
23
|
+
To run the React component tests (`FunnelCard.test.tsx`):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Install testing libraries
|
|
27
|
+
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Optional: Storybook
|
|
31
|
+
|
|
32
|
+
To view the Storybook stories (`FunnelCard.stories.tsx`):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Initialize Storybook
|
|
36
|
+
npx storybook@latest init
|
|
37
|
+
|
|
38
|
+
# Start Storybook
|
|
39
|
+
npm run storybook
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Tailwind CSS Setup
|
|
43
|
+
|
|
44
|
+
FunnelCard uses Tailwind CSS classes. If your project doesn't have Tailwind:
|
|
45
|
+
|
|
46
|
+
### 1. Install Tailwind
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install --save-dev tailwindcss postcss autoprefixer
|
|
50
|
+
npx tailwindcss init -p
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Configure Tailwind
|
|
54
|
+
|
|
55
|
+
**tailwind.config.js**:
|
|
56
|
+
```javascript
|
|
57
|
+
/** @type {import('tailwindcss').Config} */
|
|
58
|
+
module.exports = {
|
|
59
|
+
content: [
|
|
60
|
+
'./src/**/*.{ts,tsx}',
|
|
61
|
+
],
|
|
62
|
+
theme: {
|
|
63
|
+
extend: {},
|
|
64
|
+
},
|
|
65
|
+
plugins: [],
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Add Tailwind Directives
|
|
70
|
+
|
|
71
|
+
Create `src/styles.css`:
|
|
72
|
+
```css
|
|
73
|
+
@tailwind base;
|
|
74
|
+
@tailwind components;
|
|
75
|
+
@tailwind utilities;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Import in your entry file:
|
|
79
|
+
```typescript
|
|
80
|
+
import './styles.css';
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Verify Installation
|
|
84
|
+
|
|
85
|
+
Test that components compile:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Type check
|
|
89
|
+
npm run type-check
|
|
90
|
+
|
|
91
|
+
# Run tests (data validation tests work without React)
|
|
92
|
+
npm test src/components/FunnelCard/FunnelCard.test.ts
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Usage in Your Project
|
|
96
|
+
|
|
97
|
+
### Import Components
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { FunnelCard } from '@simpli/funnels';
|
|
101
|
+
|
|
102
|
+
// Or import sub-components directly
|
|
103
|
+
import { StatusBadge, StageIndicator, MatchBar, FunnelStats } from '@simpli/funnels';
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Basic Example
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { FunnelCard } from '@simpli/funnels';
|
|
110
|
+
|
|
111
|
+
function MyComponent() {
|
|
112
|
+
const funnel = {
|
|
113
|
+
id: 'funnel-1',
|
|
114
|
+
name: 'Test Funnel',
|
|
115
|
+
status: 'active',
|
|
116
|
+
input_type: 'any',
|
|
117
|
+
stages: [
|
|
118
|
+
{
|
|
119
|
+
id: 'stage-1',
|
|
120
|
+
order: 0,
|
|
121
|
+
name: 'Stage 1',
|
|
122
|
+
filter_logic: 'AND',
|
|
123
|
+
rules: [{ field_path: 'name', operator: 'eq', value: 'test' }],
|
|
124
|
+
match_action: 'continue',
|
|
125
|
+
no_match_action: 'exclude',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
created_at: new Date().toISOString(),
|
|
129
|
+
updated_at: new Date().toISOString(),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const latestRun = {
|
|
133
|
+
id: 'run-1',
|
|
134
|
+
funnel_id: 'funnel-1',
|
|
135
|
+
status: 'completed',
|
|
136
|
+
trigger_type: 'manual',
|
|
137
|
+
started_at: new Date().toISOString(),
|
|
138
|
+
total_input: 1000,
|
|
139
|
+
total_matched: 50,
|
|
140
|
+
total_excluded: 950,
|
|
141
|
+
total_tagged: 50,
|
|
142
|
+
stage_stats: {},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<FunnelCard
|
|
147
|
+
funnel={funnel}
|
|
148
|
+
latestRun={latestRun}
|
|
149
|
+
onViewFlow={(f) => console.log('View:', f.id)}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Integration with Next.js
|
|
156
|
+
|
|
157
|
+
If using in a Next.js app:
|
|
158
|
+
|
|
159
|
+
### 1. Mark as Client Component
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
'use client';
|
|
163
|
+
|
|
164
|
+
import { FunnelCard } from '@simpli/funnels';
|
|
165
|
+
|
|
166
|
+
export default function FunnelsPage() {
|
|
167
|
+
// ...
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 2. Import Tailwind Styles
|
|
172
|
+
|
|
173
|
+
In `app/layout.tsx` or `_app.tsx`:
|
|
174
|
+
```tsx
|
|
175
|
+
import '@simpli/funnels/dist/styles.css'; // If package exports styles
|
|
176
|
+
// OR import Tailwind directly:
|
|
177
|
+
import 'tailwindcss/tailwind.css';
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Integration with Django Backend
|
|
181
|
+
|
|
182
|
+
If using with Django API:
|
|
183
|
+
|
|
184
|
+
### 1. Fetch Funnel Data
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
async function fetchFunnels() {
|
|
188
|
+
const response = await fetch('/api/funnels/', {
|
|
189
|
+
headers: {
|
|
190
|
+
'Authorization': `Bearer ${token}`,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
return response.json();
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 2. Render Cards
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
function FunnelDashboard() {
|
|
201
|
+
const [funnels, setFunnels] = useState([]);
|
|
202
|
+
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
fetchFunnels().then(setFunnels);
|
|
205
|
+
}, []);
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
209
|
+
{funnels.map(funnel => (
|
|
210
|
+
<FunnelCard
|
|
211
|
+
key={funnel.id}
|
|
212
|
+
funnel={funnel}
|
|
213
|
+
latestRun={funnel.latest_run}
|
|
214
|
+
onViewFlow={(f) => router.push(`/funnels/${f.id}`)}
|
|
215
|
+
/>
|
|
216
|
+
))}
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Troubleshooting
|
|
223
|
+
|
|
224
|
+
### Issue: "Cannot find module 'react'"
|
|
225
|
+
|
|
226
|
+
**Solution**: Install React dependencies
|
|
227
|
+
```bash
|
|
228
|
+
npm install react react-dom @types/react @types/react-dom
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Issue: "Unknown at rule @tailwind"
|
|
232
|
+
|
|
233
|
+
**Solution**: Install and configure Tailwind CSS (see above)
|
|
234
|
+
|
|
235
|
+
### Issue: Tests failing with "ReferenceError: React is not defined"
|
|
236
|
+
|
|
237
|
+
**Solution**: Import React in test file
|
|
238
|
+
```typescript
|
|
239
|
+
import React from 'react';
|
|
240
|
+
import { render } from '@testing-library/react';
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Issue: Styles not applying
|
|
244
|
+
|
|
245
|
+
**Solution**: Ensure Tailwind is configured and CSS is imported
|
|
246
|
+
```typescript
|
|
247
|
+
import 'tailwindcss/tailwind.css';
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Issue: Type errors in Storybook
|
|
251
|
+
|
|
252
|
+
**Solution**: Install Storybook types
|
|
253
|
+
```bash
|
|
254
|
+
npm install --save-dev @storybook/react @storybook/addon-essentials
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Production Build
|
|
258
|
+
|
|
259
|
+
When building for production:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# Build package
|
|
263
|
+
npm run build
|
|
264
|
+
|
|
265
|
+
# Or if using with bundler (webpack, vite, etc.)
|
|
266
|
+
# Components will be tree-shaken automatically
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## CDN Usage (Not Recommended)
|
|
270
|
+
|
|
271
|
+
For quick prototyping only:
|
|
272
|
+
|
|
273
|
+
```html
|
|
274
|
+
<!-- React -->
|
|
275
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
|
276
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
277
|
+
|
|
278
|
+
<!-- Tailwind CSS -->
|
|
279
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
280
|
+
|
|
281
|
+
<!-- Your bundled package -->
|
|
282
|
+
<script src="/dist/funnels.umd.js"></script>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Use npm install for production.
|
|
286
|
+
|
|
287
|
+
## Next Steps
|
|
288
|
+
|
|
289
|
+
1. ✓ Install React dependencies
|
|
290
|
+
2. ✓ Configure Tailwind CSS
|
|
291
|
+
3. ✓ Import FunnelCard component
|
|
292
|
+
4. ✓ Pass funnel and latestRun props
|
|
293
|
+
5. ✓ Handle onViewFlow callback
|
|
294
|
+
6. View README.md for detailed usage
|
|
295
|
+
7. View DESIGN.md for design specifications
|
|
296
|
+
8. Check out Storybook for visual examples
|
|
297
|
+
|
|
298
|
+
## Questions?
|
|
299
|
+
|
|
300
|
+
See other documentation files:
|
|
301
|
+
- `README.md` - Component usage guide
|
|
302
|
+
- `DESIGN.md` - Design specifications and rationale
|
|
303
|
+
- `IMPLEMENTATION_SUMMARY.md` - Complete implementation details
|
|
304
|
+
- `FunnelCard.tsx` - Source code with inline comments
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MatchBar Component
|
|
3
|
+
*
|
|
4
|
+
* Progress bar showing match percentage with count label.
|
|
5
|
+
*
|
|
6
|
+
* Design Rationale:
|
|
7
|
+
* - Green gradient conveys success/completion
|
|
8
|
+
* - Rounded corners match card aesthetic
|
|
9
|
+
* - Label shows absolute count (more actionable than percentage alone)
|
|
10
|
+
* - Gray background shows full scale
|
|
11
|
+
*
|
|
12
|
+
* Accessibility:
|
|
13
|
+
* - Text label provides non-visual indication of value
|
|
14
|
+
* - Sufficient contrast between bar and background
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface MatchBarProps {
|
|
18
|
+
matched: number;
|
|
19
|
+
total: number;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function MatchBar({ matched, total, className = '' }: MatchBarProps) {
|
|
24
|
+
const percentage = total > 0 ? Math.round((matched / total) * 100) : 0;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className={`space-y-1 ${className}`}>
|
|
28
|
+
{/* Progress bar */}
|
|
29
|
+
<div className="relative h-6 bg-gray-200 rounded-md overflow-hidden">
|
|
30
|
+
<div
|
|
31
|
+
className="absolute inset-y-0 left-0 bg-gradient-to-r from-green-500 to-green-600 transition-all duration-300"
|
|
32
|
+
style={{ width: `${percentage}%` }}
|
|
33
|
+
role="progressbar"
|
|
34
|
+
aria-valuenow={percentage}
|
|
35
|
+
aria-valuemin={0}
|
|
36
|
+
aria-valuemax={100}
|
|
37
|
+
aria-label={`${matched} of ${total} matched`}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{/* Match count label */}
|
|
42
|
+
<div className="text-right">
|
|
43
|
+
<span className="text-sm font-medium text-gray-700">
|
|
44
|
+
{matched.toLocaleString()} matched
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# FunnelCard Component
|
|
2
|
+
|
|
3
|
+
BRUTALLY GENERIC funnel card component matching Django admin screenshot design.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
FunnelCard displays a funnel overview with:
|
|
8
|
+
- **Header**: Funnel name + color-coded status badge
|
|
9
|
+
- **Description**: Optional descriptive text
|
|
10
|
+
- **Stage List**: Sequential stages with numbered indicators and rule counts
|
|
11
|
+
- **Match Bar**: Visual progress bar showing match percentage
|
|
12
|
+
- **Stats**: Three-column metrics (INPUT / MATCHED / EXCLUDED)
|
|
13
|
+
- **Action**: "View Flow" button
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { FunnelCard } from '@simpli/funnels';
|
|
19
|
+
|
|
20
|
+
function MyComponent() {
|
|
21
|
+
const funnel = {
|
|
22
|
+
id: 'funnel-1',
|
|
23
|
+
name: 'Series A Investor Qualification',
|
|
24
|
+
description: 'Identifies qualified Series A investors',
|
|
25
|
+
status: 'active',
|
|
26
|
+
input_type: 'contacts',
|
|
27
|
+
stages: [
|
|
28
|
+
{
|
|
29
|
+
id: 'stage-1',
|
|
30
|
+
order: 0,
|
|
31
|
+
name: 'Stage Filter',
|
|
32
|
+
filter_logic: 'AND',
|
|
33
|
+
rules: [
|
|
34
|
+
{ field_path: 'firm.stage', operator: 'in', value: ['Series A'] }
|
|
35
|
+
],
|
|
36
|
+
match_action: 'continue',
|
|
37
|
+
no_match_action: 'exclude',
|
|
38
|
+
},
|
|
39
|
+
// ... more stages
|
|
40
|
+
],
|
|
41
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
42
|
+
updated_at: '2024-01-01T00:00:00Z',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const latestRun = {
|
|
46
|
+
id: 'run-1',
|
|
47
|
+
funnel_id: 'funnel-1',
|
|
48
|
+
status: 'completed',
|
|
49
|
+
trigger_type: 'manual',
|
|
50
|
+
started_at: '2024-01-01T00:00:00Z',
|
|
51
|
+
completed_at: '2024-01-01T00:01:00Z',
|
|
52
|
+
total_input: 83061,
|
|
53
|
+
total_matched: 235,
|
|
54
|
+
total_excluded: 82826,
|
|
55
|
+
total_tagged: 235,
|
|
56
|
+
stage_stats: {},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<FunnelCard
|
|
61
|
+
funnel={funnel}
|
|
62
|
+
latestRun={latestRun}
|
|
63
|
+
onViewFlow={(funnel) => console.log('View:', funnel.id)}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Props
|
|
70
|
+
|
|
71
|
+
| Prop | Type | Required | Description |
|
|
72
|
+
|------|------|----------|-------------|
|
|
73
|
+
| `funnel` | `Funnel` | Yes | Funnel definition (BRUTALLY GENERIC) |
|
|
74
|
+
| `latestRun` | `FunnelRun` | No | Latest run results. Shows "no runs yet" if missing |
|
|
75
|
+
| `onViewFlow` | `(funnel: Funnel) => void` | No | Callback when "View Flow" button clicked |
|
|
76
|
+
| `onEdit` | `(funnel: Funnel) => void` | No | Callback for edit action (future use) |
|
|
77
|
+
| `className` | `string` | No | Additional CSS classes |
|
|
78
|
+
|
|
79
|
+
## Visual States
|
|
80
|
+
|
|
81
|
+
### Status Badges
|
|
82
|
+
- **ACTIVE** - Green badge (bg-green-100, text-green-800)
|
|
83
|
+
- **DRAFT** - Yellow badge (bg-yellow-100, text-yellow-800)
|
|
84
|
+
- **PAUSED** - Gray badge (bg-gray-100, text-gray-800)
|
|
85
|
+
- **ARCHIVED** - Red badge (bg-red-100, text-red-800)
|
|
86
|
+
|
|
87
|
+
### Run States
|
|
88
|
+
- **Completed** - Shows match bar + stats
|
|
89
|
+
- **Running** - Shows "Running..." message
|
|
90
|
+
- **Failed** - Shows "Last run failed" message
|
|
91
|
+
- **No runs** - Shows "No runs yet" message
|
|
92
|
+
|
|
93
|
+
### Empty States
|
|
94
|
+
- **No stages** - Shows "No stages defined" in stage section
|
|
95
|
+
- **No description** - Description section omitted
|
|
96
|
+
- **Zero matches** - Match bar at 0%, stats show zeros
|
|
97
|
+
|
|
98
|
+
## Sub-Components
|
|
99
|
+
|
|
100
|
+
All sub-components are exported for composition flexibility:
|
|
101
|
+
|
|
102
|
+
### StatusBadge
|
|
103
|
+
```tsx
|
|
104
|
+
import { StatusBadge } from '@simpli/funnels';
|
|
105
|
+
|
|
106
|
+
<StatusBadge status="active" />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### StageIndicator
|
|
110
|
+
```tsx
|
|
111
|
+
import { StageIndicator } from '@simpli/funnels';
|
|
112
|
+
|
|
113
|
+
<StageIndicator
|
|
114
|
+
order={0}
|
|
115
|
+
name="Stage Filter"
|
|
116
|
+
ruleCount={3}
|
|
117
|
+
isLast={false}
|
|
118
|
+
/>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### MatchBar
|
|
122
|
+
```tsx
|
|
123
|
+
import { MatchBar } from '@simpli/funnels';
|
|
124
|
+
|
|
125
|
+
<MatchBar matched={235} total={83061} />
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### FunnelStats
|
|
129
|
+
```tsx
|
|
130
|
+
import { FunnelStats } from '@simpli/funnels';
|
|
131
|
+
|
|
132
|
+
<FunnelStats input={83061} matched={235} excluded={82826} />
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Design Specifications
|
|
136
|
+
|
|
137
|
+
### Layout
|
|
138
|
+
```
|
|
139
|
+
┌─────────────────────────────────────────┐
|
|
140
|
+
│ Funnel Name [ACTIVE badge] │ Header
|
|
141
|
+
│ Description text here... │
|
|
142
|
+
│ │
|
|
143
|
+
│ ① Stage 1 Name 1 rule │ Stages
|
|
144
|
+
│ │ │
|
|
145
|
+
│ ② Stage 2 Name 3 rules │
|
|
146
|
+
│ │ │
|
|
147
|
+
│ ③ Stage 3 Name 2 rules │
|
|
148
|
+
│ │
|
|
149
|
+
│ ████████████░░░░░░░░ 235 matched │ Match Bar
|
|
150
|
+
│ │
|
|
151
|
+
│ 83061 INPUT 235 MATCHED 82826 EXCLUDED │ Stats
|
|
152
|
+
│ │
|
|
153
|
+
│ [View Flow →] │ Action
|
|
154
|
+
└─────────────────────────────────────────┘
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Spacing
|
|
158
|
+
- Card padding: 24px (px-6)
|
|
159
|
+
- Section spacing: 16px (py-4)
|
|
160
|
+
- Stage gap: 0px (vertical line connects)
|
|
161
|
+
- Stats grid gap: 8px (gap-2)
|
|
162
|
+
|
|
163
|
+
### Colors
|
|
164
|
+
- Card: white background, gray-200 border, shadow-sm
|
|
165
|
+
- Status Active: green-100 bg, green-800 text
|
|
166
|
+
- Status Draft: yellow-100 bg, yellow-800 text
|
|
167
|
+
- Status Paused: gray-100 bg, gray-800 text
|
|
168
|
+
- Status Archived: red-100 bg, red-800 text
|
|
169
|
+
- Match Bar: green-500 to green-600 gradient
|
|
170
|
+
- Stats INPUT: blue-50 bg, blue-600 text
|
|
171
|
+
- Stats MATCHED: green-50 bg, green-600 text
|
|
172
|
+
- Stats EXCLUDED: red-50 bg, red-600 text
|
|
173
|
+
|
|
174
|
+
### Typography
|
|
175
|
+
- Funnel name: text-lg, font-semibold
|
|
176
|
+
- Description: text-sm, text-gray-600
|
|
177
|
+
- Stage name: text-sm, font-medium
|
|
178
|
+
- Rule count: text-xs, text-gray-500
|
|
179
|
+
- Stats label: text-xs, font-medium
|
|
180
|
+
- Stats value: text-lg, font-bold
|
|
181
|
+
- Button: text-sm, font-medium
|
|
182
|
+
|
|
183
|
+
### Responsive Behavior
|
|
184
|
+
- Mobile (< 640px): Full width, single column
|
|
185
|
+
- Tablet (640px - 1024px): Constrained width
|
|
186
|
+
- Desktop (> 1024px): Max width, centered
|
|
187
|
+
|
|
188
|
+
## Accessibility
|
|
189
|
+
|
|
190
|
+
### Semantic HTML
|
|
191
|
+
- `<article>` for card container
|
|
192
|
+
- `<header>` for funnel name + status
|
|
193
|
+
- `<section>` for each major area
|
|
194
|
+
- `<footer>` for action button
|
|
195
|
+
- `<dl>/<dt>/<dd>` for stats
|
|
196
|
+
|
|
197
|
+
### ARIA Attributes
|
|
198
|
+
- `aria-label` on article: "Funnel: {name}"
|
|
199
|
+
- `aria-label` on sections: "Funnel stages", "Match results", etc.
|
|
200
|
+
- `aria-label` on button: "View flow details for {name}"
|
|
201
|
+
- `role="progressbar"` on match bar with aria-value* attributes
|
|
202
|
+
|
|
203
|
+
### Keyboard Navigation
|
|
204
|
+
- Button is fully keyboard accessible
|
|
205
|
+
- Focus visible with ring-2 ring-blue-500
|
|
206
|
+
- Tab order follows visual flow
|
|
207
|
+
|
|
208
|
+
### Color Contrast
|
|
209
|
+
- All text meets WCAG AA 4.5:1 minimum
|
|
210
|
+
- Status badges: 4.5:1 contrast
|
|
211
|
+
- Stats: 4.5:1 contrast
|
|
212
|
+
- Button: 4.5:1 contrast
|
|
213
|
+
|
|
214
|
+
## Examples
|
|
215
|
+
|
|
216
|
+
### Investor Funnel
|
|
217
|
+
```tsx
|
|
218
|
+
<FunnelCard
|
|
219
|
+
funnel={{
|
|
220
|
+
name: 'Series A Investor Qualification',
|
|
221
|
+
status: 'active',
|
|
222
|
+
stages: [/* ... */],
|
|
223
|
+
// ...
|
|
224
|
+
}}
|
|
225
|
+
latestRun={{
|
|
226
|
+
total_input: 83061,
|
|
227
|
+
total_matched: 235,
|
|
228
|
+
total_excluded: 82826,
|
|
229
|
+
// ...
|
|
230
|
+
}}
|
|
231
|
+
/>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Recipe Funnel
|
|
235
|
+
```tsx
|
|
236
|
+
<FunnelCard
|
|
237
|
+
funnel={{
|
|
238
|
+
name: 'Quick Easy Dinner Recommendations',
|
|
239
|
+
status: 'active',
|
|
240
|
+
stages: [/* ... */],
|
|
241
|
+
// ...
|
|
242
|
+
}}
|
|
243
|
+
latestRun={{
|
|
244
|
+
total_input: 5420,
|
|
245
|
+
total_matched: 87,
|
|
246
|
+
total_excluded: 5333,
|
|
247
|
+
// ...
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Lead Scoring Funnel
|
|
253
|
+
```tsx
|
|
254
|
+
<FunnelCard
|
|
255
|
+
funnel={{
|
|
256
|
+
name: 'Enterprise Lead Scoring',
|
|
257
|
+
status: 'active',
|
|
258
|
+
stages: [/* ... */],
|
|
259
|
+
// ...
|
|
260
|
+
}}
|
|
261
|
+
latestRun={{
|
|
262
|
+
total_input: 12500,
|
|
263
|
+
total_matched: 342,
|
|
264
|
+
total_excluded: 12158,
|
|
265
|
+
// ...
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Testing
|
|
271
|
+
|
|
272
|
+
Run tests:
|
|
273
|
+
```bash
|
|
274
|
+
npm test src/components/FunnelCard/FunnelCard.test.tsx
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
View stories:
|
|
278
|
+
```bash
|
|
279
|
+
npm run storybook
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Dependencies
|
|
283
|
+
|
|
284
|
+
- React (peer dependency)
|
|
285
|
+
- Tailwind CSS (styling)
|
|
286
|
+
- TypeScript (type safety)
|
|
287
|
+
|
|
288
|
+
## Browser Support
|
|
289
|
+
|
|
290
|
+
- Chrome/Edge: Latest 2 versions
|
|
291
|
+
- Firefox: Latest 2 versions
|
|
292
|
+
- Safari: Latest 2 versions
|
|
293
|
+
- Mobile Safari: Latest 2 versions
|
|
294
|
+
- Chrome Android: Latest 2 versions
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StageIndicator Component
|
|
3
|
+
*
|
|
4
|
+
* Displays a numbered circle badge for funnel stages with connecting vertical line.
|
|
5
|
+
*
|
|
6
|
+
* Design Rationale:
|
|
7
|
+
* - Numbered circles (①②③) provide clear sequential ordering
|
|
8
|
+
* - Vertical connecting line shows flow/progression
|
|
9
|
+
* - Compact layout conserves vertical space
|
|
10
|
+
* - Gray text for rule count de-emphasizes secondary info
|
|
11
|
+
*
|
|
12
|
+
* Accessibility:
|
|
13
|
+
* - Sufficient color contrast (4.5:1 minimum)
|
|
14
|
+
* - Semantic HTML structure
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface StageIndicatorProps {
|
|
18
|
+
order: number;
|
|
19
|
+
name: string;
|
|
20
|
+
ruleCount: number;
|
|
21
|
+
isLast?: boolean;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function StageIndicator({
|
|
26
|
+
order,
|
|
27
|
+
name,
|
|
28
|
+
ruleCount,
|
|
29
|
+
isLast = false,
|
|
30
|
+
className = ''
|
|
31
|
+
}: StageIndicatorProps) {
|
|
32
|
+
// Format order as circled number (1-20 supported by Unicode)
|
|
33
|
+
const circledNumber = order < 20
|
|
34
|
+
? String.fromCharCode(9312 + order) // ① = U+2460
|
|
35
|
+
: `(${order + 1})`;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={`flex items-start gap-2 ${className}`}>
|
|
39
|
+
{/* Stage number circle with connecting line */}
|
|
40
|
+
<div className="flex flex-col items-center">
|
|
41
|
+
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 text-blue-800 flex items-center justify-center text-sm font-medium">
|
|
42
|
+
{circledNumber}
|
|
43
|
+
</div>
|
|
44
|
+
{!isLast && (
|
|
45
|
+
<div className="w-0.5 h-6 bg-gray-200 mt-1" />
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* Stage name and rule count */}
|
|
50
|
+
<div className="flex-1 pt-0.5 min-w-0">
|
|
51
|
+
<div className="flex items-baseline justify-between gap-2">
|
|
52
|
+
<span className="text-sm font-medium text-gray-900 truncate">
|
|
53
|
+
{name}
|
|
54
|
+
</span>
|
|
55
|
+
<span className="text-xs text-gray-500 whitespace-nowrap">
|
|
56
|
+
{ruleCount} {ruleCount === 1 ? 'rule' : 'rules'}
|
|
57
|
+
</span>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|