@contractspec/example.crm-pipeline 3.7.6 → 3.7.7

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 (105) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/AGENTS.md +51 -33
  3. package/README.md +66 -148
  4. package/dist/browser/events/contact.event.js +1 -1
  5. package/dist/browser/events/deal.event.js +1 -1
  6. package/dist/browser/events/index.js +3 -3
  7. package/dist/browser/events/task.event.js +1 -1
  8. package/dist/browser/index.js +293 -293
  9. package/dist/browser/ui/CrmDashboard.js +221 -221
  10. package/dist/browser/ui/CrmDealCard.js +5 -5
  11. package/dist/browser/ui/CrmPipelineBoard.js +13 -13
  12. package/dist/browser/ui/hooks/index.js +2 -2
  13. package/dist/browser/ui/hooks/useDealList.js +1 -1
  14. package/dist/browser/ui/hooks/useDealMutations.js +1 -1
  15. package/dist/browser/ui/index.js +290 -290
  16. package/dist/browser/ui/modals/CreateDealModal.js +12 -12
  17. package/dist/browser/ui/modals/DealActionsModal.js +21 -21
  18. package/dist/browser/ui/modals/index.js +33 -33
  19. package/dist/browser/ui/renderers/index.js +116 -116
  20. package/dist/browser/ui/renderers/pipeline.renderer.js +97 -97
  21. package/dist/deal/index.d.ts +2 -2
  22. package/dist/events/contact.event.js +1 -1
  23. package/dist/events/deal.event.js +1 -1
  24. package/dist/events/index.js +3 -3
  25. package/dist/events/task.event.js +1 -1
  26. package/dist/handlers/index.d.ts +2 -2
  27. package/dist/index.d.ts +3 -3
  28. package/dist/index.js +293 -293
  29. package/dist/node/events/contact.event.js +1 -1
  30. package/dist/node/events/deal.event.js +1 -1
  31. package/dist/node/events/index.js +3 -3
  32. package/dist/node/events/task.event.js +1 -1
  33. package/dist/node/index.js +293 -293
  34. package/dist/node/ui/CrmDashboard.js +221 -221
  35. package/dist/node/ui/CrmDealCard.js +5 -5
  36. package/dist/node/ui/CrmPipelineBoard.js +13 -13
  37. package/dist/node/ui/hooks/index.js +2 -2
  38. package/dist/node/ui/hooks/useDealList.js +1 -1
  39. package/dist/node/ui/hooks/useDealMutations.js +1 -1
  40. package/dist/node/ui/index.js +290 -290
  41. package/dist/node/ui/modals/CreateDealModal.js +12 -12
  42. package/dist/node/ui/modals/DealActionsModal.js +21 -21
  43. package/dist/node/ui/modals/index.js +33 -33
  44. package/dist/node/ui/renderers/index.js +116 -116
  45. package/dist/node/ui/renderers/pipeline.renderer.js +97 -97
  46. package/dist/operations/index.d.ts +1 -1
  47. package/dist/ui/CrmDashboard.js +221 -221
  48. package/dist/ui/CrmDealCard.js +5 -5
  49. package/dist/ui/CrmPipelineBoard.js +13 -13
  50. package/dist/ui/hooks/index.d.ts +2 -2
  51. package/dist/ui/hooks/index.js +2 -2
  52. package/dist/ui/hooks/useDealList.js +1 -1
  53. package/dist/ui/hooks/useDealMutations.d.ts +9 -0
  54. package/dist/ui/hooks/useDealMutations.js +1 -1
  55. package/dist/ui/index.d.ts +3 -3
  56. package/dist/ui/index.js +290 -290
  57. package/dist/ui/modals/CreateDealModal.js +12 -12
  58. package/dist/ui/modals/DealActionsModal.js +21 -21
  59. package/dist/ui/modals/index.js +33 -33
  60. package/dist/ui/renderers/index.d.ts +1 -1
  61. package/dist/ui/renderers/index.js +116 -116
  62. package/dist/ui/renderers/pipeline.renderer.d.ts +1 -1
  63. package/dist/ui/renderers/pipeline.renderer.js +97 -97
  64. package/package.json +10 -10
  65. package/src/crm-pipeline.feature.ts +86 -86
  66. package/src/deal/deal.enum.ts +8 -8
  67. package/src/deal/deal.operation.ts +255 -255
  68. package/src/deal/deal.schema.ts +92 -92
  69. package/src/deal/deal.test-spec.ts +48 -48
  70. package/src/deal/index.ts +17 -19
  71. package/src/docs/crm-pipeline.docblock.ts +43 -43
  72. package/src/entities/company.entity.ts +52 -52
  73. package/src/entities/contact.entity.ts +67 -67
  74. package/src/entities/deal.entity.ts +134 -134
  75. package/src/entities/index.ts +27 -27
  76. package/src/entities/task.entity.ts +105 -105
  77. package/src/events/contact.event.ts +22 -22
  78. package/src/events/deal.event.ts +77 -77
  79. package/src/events/task.event.ts +19 -19
  80. package/src/example.ts +32 -32
  81. package/src/handlers/crm.handlers.ts +358 -357
  82. package/src/handlers/deal.handlers.ts +179 -179
  83. package/src/handlers/index.ts +18 -19
  84. package/src/handlers/mock-data.ts +167 -167
  85. package/src/index.ts +11 -11
  86. package/src/operations/index.ts +16 -16
  87. package/src/presentations/dashboard.presentation.ts +45 -45
  88. package/src/presentations/pipeline.presentation.ts +90 -90
  89. package/src/seeders/index.ts +26 -26
  90. package/src/shared/overlay-types.ts +23 -23
  91. package/src/ui/CrmDashboard.tsx +256 -256
  92. package/src/ui/CrmDealCard.tsx +64 -64
  93. package/src/ui/CrmPipelineBoard.tsx +105 -105
  94. package/src/ui/hooks/index.ts +3 -3
  95. package/src/ui/hooks/useDealList.ts +85 -85
  96. package/src/ui/hooks/useDealMutations.ts +151 -150
  97. package/src/ui/index.ts +5 -10
  98. package/src/ui/modals/CreateDealModal.tsx +217 -217
  99. package/src/ui/modals/DealActionsModal.tsx +390 -390
  100. package/src/ui/overlays/demo-overlays.ts +43 -43
  101. package/src/ui/renderers/index.ts +4 -3
  102. package/src/ui/renderers/pipeline.markdown.ts +165 -165
  103. package/src/ui/renderers/pipeline.renderer.tsx +17 -16
  104. package/tsconfig.json +7 -8
  105. package/tsdown.config.js +7 -3
@@ -1,5 +1,18 @@
1
1
  'use client';
2
2
 
3
+ import {
4
+ Button,
5
+ ErrorState,
6
+ LoaderBlock,
7
+ StatCard,
8
+ StatCardGroup,
9
+ } from '@contractspec/lib.design-system';
10
+ import {
11
+ Tabs,
12
+ TabsContent,
13
+ TabsList,
14
+ TabsTrigger,
15
+ } from '@contractspec/lib.ui-kit-web/ui/tabs';
3
16
  /**
4
17
  * CRM Dashboard
5
18
  *
@@ -13,299 +26,286 @@
13
26
  * - LoseDealContract -> Mark deal as lost
14
27
  */
15
28
  import { useCallback, useState } from 'react';
16
- import {
17
- Button,
18
- ErrorState,
19
- LoaderBlock,
20
- StatCard,
21
- StatCardGroup,
22
- } from '@contractspec/lib.design-system';
23
- import {
24
- Tabs,
25
- TabsContent,
26
- TabsList,
27
- TabsTrigger,
28
- } from '@contractspec/lib.ui-kit-web/ui/tabs';
29
+ import { CrmPipelineBoard } from './CrmPipelineBoard';
29
30
  import { type Deal, useDealList } from './hooks/useDealList';
30
31
  import { useDealMutations } from './hooks/useDealMutations';
31
- import { CrmPipelineBoard } from './CrmPipelineBoard';
32
32
  import { CreateDealModal } from './modals/CreateDealModal';
33
33
  import { DealActionsModal } from './modals/DealActionsModal';
34
34
 
35
35
  // type Tab = 'pipeline' | 'list' | 'metrics';
36
36
 
37
37
  function formatCurrency(value: number, currency = 'USD'): string {
38
- return new Intl.NumberFormat('en-US', {
39
- style: 'currency',
40
- currency,
41
- minimumFractionDigits: 0,
42
- maximumFractionDigits: 0,
43
- }).format(value);
38
+ return new Intl.NumberFormat('en-US', {
39
+ style: 'currency',
40
+ currency,
41
+ minimumFractionDigits: 0,
42
+ maximumFractionDigits: 0,
43
+ }).format(value);
44
44
  }
45
45
 
46
46
  export function CrmDashboard() {
47
- const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
48
- const [selectedDeal, setSelectedDeal] = useState<Deal | null>(null);
49
- const [isDealActionsOpen, setIsDealActionsOpen] = useState(false);
47
+ const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
48
+ const [selectedDeal, setSelectedDeal] = useState<Deal | null>(null);
49
+ const [isDealActionsOpen, setIsDealActionsOpen] = useState(false);
50
50
 
51
- const { data, dealsByStage, stages, loading, error, stats, refetch } =
52
- useDealList();
51
+ const { data, dealsByStage, stages, loading, error, stats, refetch } =
52
+ useDealList();
53
53
 
54
- const mutations = useDealMutations({
55
- onSuccess: () => {
56
- refetch();
57
- },
58
- });
54
+ const mutations = useDealMutations({
55
+ onSuccess: () => {
56
+ refetch();
57
+ },
58
+ });
59
59
 
60
- const handleDealClick = useCallback(
61
- (dealId: string) => {
62
- // Find deal in data
63
- const deal = dealsByStage
64
- ? Object.values(dealsByStage)
65
- .flat()
66
- .find((d) => d.id === dealId)
67
- : null;
60
+ const handleDealClick = useCallback(
61
+ (dealId: string) => {
62
+ // Find deal in data
63
+ const deal = dealsByStage
64
+ ? Object.values(dealsByStage)
65
+ .flat()
66
+ .find((d) => d.id === dealId)
67
+ : null;
68
68
 
69
- if (deal) {
70
- setSelectedDeal(deal);
71
- setIsDealActionsOpen(true);
72
- }
73
- },
74
- [dealsByStage]
75
- );
69
+ if (deal) {
70
+ setSelectedDeal(deal);
71
+ setIsDealActionsOpen(true);
72
+ }
73
+ },
74
+ [dealsByStage]
75
+ );
76
76
 
77
- const handleDealMove = useCallback(
78
- async (dealId: string, toStageId: string) => {
79
- await mutations.moveDeal({ dealId, stageId: toStageId });
80
- },
81
- [mutations]
82
- );
77
+ const handleDealMove = useCallback(
78
+ async (dealId: string, toStageId: string) => {
79
+ await mutations.moveDeal({ dealId, stageId: toStageId });
80
+ },
81
+ [mutations]
82
+ );
83
83
 
84
- if (loading && !data) {
85
- return <LoaderBlock label="Loading CRM..." />;
86
- }
84
+ if (loading && !data) {
85
+ return <LoaderBlock label="Loading CRM..." />;
86
+ }
87
87
 
88
- if (error) {
89
- return (
90
- <ErrorState
91
- title="Failed to load CRM"
92
- description={error.message}
93
- onRetry={refetch}
94
- retryLabel="Retry"
95
- />
96
- );
97
- }
88
+ if (error) {
89
+ return (
90
+ <ErrorState
91
+ title="Failed to load CRM"
92
+ description={error.message}
93
+ onRetry={refetch}
94
+ retryLabel="Retry"
95
+ />
96
+ );
97
+ }
98
98
 
99
- return (
100
- <div className="space-y-6">
101
- {/* Header with Create Button */}
102
- <div className="flex items-center justify-between">
103
- <h2 className="text-2xl font-bold">CRM Pipeline</h2>
104
- <Button onClick={() => setIsCreateModalOpen(true)}>
105
- <span className="mr-2">+</span> Create Deal
106
- </Button>
107
- </div>
99
+ return (
100
+ <div className="space-y-6">
101
+ {/* Header with Create Button */}
102
+ <div className="flex items-center justify-between">
103
+ <h2 className="font-bold text-2xl">CRM Pipeline</h2>
104
+ <Button onClick={() => setIsCreateModalOpen(true)}>
105
+ <span className="mr-2">+</span> Create Deal
106
+ </Button>
107
+ </div>
108
108
 
109
- {/* Stats Row */}
110
- {stats && (
111
- <StatCardGroup>
112
- <StatCard
113
- label="Total Pipeline"
114
- value={formatCurrency(stats.totalValue)}
115
- hint={`${stats.total} deals`}
116
- />
117
- <StatCard
118
- label="Open Deals"
119
- value={formatCurrency(stats.openValue)}
120
- hint={`${stats.openCount} active`}
121
- />
122
- <StatCard
123
- label="Won"
124
- value={formatCurrency(stats.wonValue)}
125
- hint={`${stats.wonCount} closed`}
126
- />
127
- <StatCard label="Lost" value={stats.lostCount} hint="deals lost" />
128
- </StatCardGroup>
129
- )}
109
+ {/* Stats Row */}
110
+ {stats && (
111
+ <StatCardGroup>
112
+ <StatCard
113
+ label="Total Pipeline"
114
+ value={formatCurrency(stats.totalValue)}
115
+ hint={`${stats.total} deals`}
116
+ />
117
+ <StatCard
118
+ label="Open Deals"
119
+ value={formatCurrency(stats.openValue)}
120
+ hint={`${stats.openCount} active`}
121
+ />
122
+ <StatCard
123
+ label="Won"
124
+ value={formatCurrency(stats.wonValue)}
125
+ hint={`${stats.wonCount} closed`}
126
+ />
127
+ <StatCard label="Lost" value={stats.lostCount} hint="deals lost" />
128
+ </StatCardGroup>
129
+ )}
130
130
 
131
- {/* Tabs */}
132
- <Tabs defaultValue="pipeline" className="w-full">
133
- <TabsList>
134
- <TabsTrigger value="pipeline">
135
- <span className="mr-2">📊</span>
136
- Pipeline
137
- </TabsTrigger>
138
- <TabsTrigger value="list">
139
- <span className="mr-2">📋</span>
140
- All Deals
141
- </TabsTrigger>
142
- <TabsTrigger value="metrics">
143
- <span className="mr-2">📈</span>
144
- Metrics
145
- </TabsTrigger>
146
- </TabsList>
131
+ {/* Tabs */}
132
+ <Tabs defaultValue="pipeline" className="w-full">
133
+ <TabsList>
134
+ <TabsTrigger value="pipeline">
135
+ <span className="mr-2">📊</span>
136
+ Pipeline
137
+ </TabsTrigger>
138
+ <TabsTrigger value="list">
139
+ <span className="mr-2">📋</span>
140
+ All Deals
141
+ </TabsTrigger>
142
+ <TabsTrigger value="metrics">
143
+ <span className="mr-2">📈</span>
144
+ Metrics
145
+ </TabsTrigger>
146
+ </TabsList>
147
147
 
148
- <TabsContent value="pipeline" className="min-h-[400px]">
149
- <CrmPipelineBoard
150
- dealsByStage={dealsByStage}
151
- stages={stages}
152
- onDealClick={handleDealClick}
153
- onDealMove={handleDealMove}
154
- />
155
- </TabsContent>
148
+ <TabsContent value="pipeline" className="min-h-[400px]">
149
+ <CrmPipelineBoard
150
+ dealsByStage={dealsByStage}
151
+ stages={stages}
152
+ onDealClick={handleDealClick}
153
+ onDealMove={handleDealMove}
154
+ />
155
+ </TabsContent>
156
156
 
157
- <TabsContent value="list" className="min-h-[400px]">
158
- <DealListTab data={data} onDealClick={handleDealClick} />
159
- </TabsContent>
157
+ <TabsContent value="list" className="min-h-[400px]">
158
+ <DealListTab data={data} onDealClick={handleDealClick} />
159
+ </TabsContent>
160
160
 
161
- <TabsContent value="metrics" className="min-h-[400px]">
162
- <MetricsTab stats={stats} />
163
- </TabsContent>
164
- </Tabs>
161
+ <TabsContent value="metrics" className="min-h-[400px]">
162
+ <MetricsTab stats={stats} />
163
+ </TabsContent>
164
+ </Tabs>
165
165
 
166
- {/* Create Deal Modal */}
167
- <CreateDealModal
168
- isOpen={isCreateModalOpen}
169
- onClose={() => setIsCreateModalOpen(false)}
170
- onSubmit={async (input) => {
171
- await mutations.createDeal(input);
172
- }}
173
- stages={stages}
174
- isLoading={mutations.createState.loading}
175
- />
166
+ {/* Create Deal Modal */}
167
+ <CreateDealModal
168
+ isOpen={isCreateModalOpen}
169
+ onClose={() => setIsCreateModalOpen(false)}
170
+ onSubmit={async (input) => {
171
+ await mutations.createDeal(input);
172
+ }}
173
+ stages={stages}
174
+ isLoading={mutations.createState.loading}
175
+ />
176
176
 
177
- {/* Deal Actions Modal */}
178
- <DealActionsModal
179
- isOpen={isDealActionsOpen}
180
- deal={selectedDeal}
181
- stages={stages}
182
- onClose={() => {
183
- setIsDealActionsOpen(false);
184
- setSelectedDeal(null);
185
- }}
186
- onWin={async (input) => {
187
- await mutations.winDeal(input);
188
- }}
189
- onLose={async (input) => {
190
- await mutations.loseDeal(input);
191
- }}
192
- onMove={async (input) => {
193
- await mutations.moveDeal(input);
194
- refetch();
195
- }}
196
- isLoading={mutations.isLoading}
197
- />
198
- </div>
199
- );
177
+ {/* Deal Actions Modal */}
178
+ <DealActionsModal
179
+ isOpen={isDealActionsOpen}
180
+ deal={selectedDeal}
181
+ stages={stages}
182
+ onClose={() => {
183
+ setIsDealActionsOpen(false);
184
+ setSelectedDeal(null);
185
+ }}
186
+ onWin={async (input) => {
187
+ await mutations.winDeal(input);
188
+ }}
189
+ onLose={async (input) => {
190
+ await mutations.loseDeal(input);
191
+ }}
192
+ onMove={async (input) => {
193
+ await mutations.moveDeal(input);
194
+ refetch();
195
+ }}
196
+ isLoading={mutations.isLoading}
197
+ />
198
+ </div>
199
+ );
200
200
  }
201
201
 
202
202
  interface DealListTabProps {
203
- data: ReturnType<typeof useDealList>['data'];
204
- onDealClick?: (dealId: string) => void;
203
+ data: ReturnType<typeof useDealList>['data'];
204
+ onDealClick?: (dealId: string) => void;
205
205
  }
206
206
 
207
207
  function DealListTab({ data, onDealClick }: DealListTabProps) {
208
- if (!data?.deals.length) {
209
- return (
210
- <div className="text-muted-foreground flex h-64 items-center justify-center">
211
- No deals found
212
- </div>
213
- );
214
- }
208
+ if (!data?.deals.length) {
209
+ return (
210
+ <div className="flex h-64 items-center justify-center text-muted-foreground">
211
+ No deals found
212
+ </div>
213
+ );
214
+ }
215
215
 
216
- return (
217
- <div className="border-border rounded-lg border">
218
- <table className="w-full">
219
- <thead className="border-border bg-muted/30 border-b">
220
- <tr>
221
- <th className="px-4 py-3 text-left text-sm font-medium">Deal</th>
222
- <th className="px-4 py-3 text-left text-sm font-medium">Value</th>
223
- <th className="px-4 py-3 text-left text-sm font-medium">Status</th>
224
- <th className="px-4 py-3 text-left text-sm font-medium">
225
- Expected Close
226
- </th>
227
- <th className="px-4 py-3 text-left text-sm font-medium">Actions</th>
228
- </tr>
229
- </thead>
230
- <tbody className="divide-border divide-y">
231
- {data.deals.map((deal: Deal) => (
232
- <tr key={deal.id} className="hover:bg-muted/50">
233
- <td className="px-4 py-3">
234
- <div className="font-medium">{deal.name}</div>
235
- </td>
236
- <td className="px-4 py-3 font-mono">
237
- {formatCurrency(deal.value, deal.currency)}
238
- </td>
239
- <td className="px-4 py-3">
240
- <span
241
- className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${
242
- deal.status === 'WON'
243
- ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
244
- : deal.status === 'LOST'
245
- ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
246
- : 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
247
- }`}
248
- >
249
- {deal.status}
250
- </span>
251
- </td>
252
- <td className="text-muted-foreground px-4 py-3">
253
- {deal.expectedCloseDate?.toLocaleDateString() ?? '-'}
254
- </td>
255
- <td className="px-4 py-3">
256
- <Button
257
- variant="ghost"
258
- size="sm"
259
- onPress={() => onDealClick?.(deal.id)}
260
- >
261
- Actions
262
- </Button>
263
- </td>
264
- </tr>
265
- ))}
266
- </tbody>
267
- </table>
268
- </div>
269
- );
216
+ return (
217
+ <div className="rounded-lg border border-border">
218
+ <table className="w-full">
219
+ <thead className="border-border border-b bg-muted/30">
220
+ <tr>
221
+ <th className="px-4 py-3 text-left font-medium text-sm">Deal</th>
222
+ <th className="px-4 py-3 text-left font-medium text-sm">Value</th>
223
+ <th className="px-4 py-3 text-left font-medium text-sm">Status</th>
224
+ <th className="px-4 py-3 text-left font-medium text-sm">
225
+ Expected Close
226
+ </th>
227
+ <th className="px-4 py-3 text-left font-medium text-sm">Actions</th>
228
+ </tr>
229
+ </thead>
230
+ <tbody className="divide-y divide-border">
231
+ {data.deals.map((deal: Deal) => (
232
+ <tr key={deal.id} className="hover:bg-muted/50">
233
+ <td className="px-4 py-3">
234
+ <div className="font-medium">{deal.name}</div>
235
+ </td>
236
+ <td className="px-4 py-3 font-mono">
237
+ {formatCurrency(deal.value, deal.currency)}
238
+ </td>
239
+ <td className="px-4 py-3">
240
+ <span
241
+ className={`inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${
242
+ deal.status === 'WON'
243
+ ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
244
+ : deal.status === 'LOST'
245
+ ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
246
+ : 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
247
+ }`}
248
+ >
249
+ {deal.status}
250
+ </span>
251
+ </td>
252
+ <td className="px-4 py-3 text-muted-foreground">
253
+ {deal.expectedCloseDate?.toLocaleDateString() ?? '-'}
254
+ </td>
255
+ <td className="px-4 py-3">
256
+ <Button
257
+ variant="ghost"
258
+ size="sm"
259
+ onPress={() => onDealClick?.(deal.id)}
260
+ >
261
+ Actions
262
+ </Button>
263
+ </td>
264
+ </tr>
265
+ ))}
266
+ </tbody>
267
+ </table>
268
+ </div>
269
+ );
270
270
  }
271
271
 
272
272
  function MetricsTab({
273
- stats,
273
+ stats,
274
274
  }: {
275
- stats: ReturnType<typeof useDealList>['stats'];
275
+ stats: ReturnType<typeof useDealList>['stats'];
276
276
  }) {
277
- if (!stats) return null;
277
+ if (!stats) return null;
278
278
 
279
- return (
280
- <div className="space-y-6">
281
- <div className="border-border bg-card rounded-xl border p-6">
282
- <h3 className="mb-4 text-lg font-semibold">Pipeline Overview</h3>
283
- <dl className="grid gap-4 sm:grid-cols-3">
284
- <div>
285
- <dt className="text-muted-foreground text-sm">Win Rate</dt>
286
- <dd className="text-2xl font-semibold">
287
- {stats.total > 0
288
- ? ((stats.wonCount / stats.total) * 100).toFixed(0)
289
- : 0}
290
- %
291
- </dd>
292
- </div>
293
- <div>
294
- <dt className="text-muted-foreground text-sm">Avg Deal Size</dt>
295
- <dd className="text-2xl font-semibold">
296
- {formatCurrency(
297
- stats.total > 0 ? stats.totalValue / stats.total : 0
298
- )}
299
- </dd>
300
- </div>
301
- <div>
302
- <dt className="text-muted-foreground text-sm">Conversion</dt>
303
- <dd className="text-2xl font-semibold">
304
- {stats.wonCount} / {stats.total}
305
- </dd>
306
- </div>
307
- </dl>
308
- </div>
309
- </div>
310
- );
279
+ return (
280
+ <div className="space-y-6">
281
+ <div className="rounded-xl border border-border bg-card p-6">
282
+ <h3 className="mb-4 font-semibold text-lg">Pipeline Overview</h3>
283
+ <dl className="grid gap-4 sm:grid-cols-3">
284
+ <div>
285
+ <dt className="text-muted-foreground text-sm">Win Rate</dt>
286
+ <dd className="font-semibold text-2xl">
287
+ {stats.total > 0
288
+ ? ((stats.wonCount / stats.total) * 100).toFixed(0)
289
+ : 0}
290
+ %
291
+ </dd>
292
+ </div>
293
+ <div>
294
+ <dt className="text-muted-foreground text-sm">Avg Deal Size</dt>
295
+ <dd className="font-semibold text-2xl">
296
+ {formatCurrency(
297
+ stats.total > 0 ? stats.totalValue / stats.total : 0
298
+ )}
299
+ </dd>
300
+ </div>
301
+ <div>
302
+ <dt className="text-muted-foreground text-sm">Conversion</dt>
303
+ <dd className="font-semibold text-2xl">
304
+ {stats.wonCount} / {stats.total}
305
+ </dd>
306
+ </div>
307
+ </dl>
308
+ </div>
309
+ </div>
310
+ );
311
311
  }