@nextsparkjs/theme-crm 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CRM_PLAN.md +343 -0
- package/about.md +122 -0
- package/config/app.config.ts +185 -0
- package/config/billing.config.ts +187 -0
- package/config/dashboard.config.ts +372 -0
- package/config/dev.config.ts +55 -0
- package/config/features.config.ts +336 -0
- package/config/flows.config.ts +511 -0
- package/config/permissions.config.ts +297 -0
- package/config/theme.config.ts +111 -0
- package/entities/activities/activities.config.ts +61 -0
- package/entities/activities/activities.fields.ts +362 -0
- package/entities/activities/activities.service.ts +503 -0
- package/entities/activities/activities.types.ts +117 -0
- package/entities/activities/messages/en.json +123 -0
- package/entities/activities/messages/es.json +123 -0
- package/entities/activities/migrations/020_activities_table.sql +123 -0
- package/entities/activities/migrations/021_activities_metas.sql +114 -0
- package/entities/activities/migrations/022_activities_sample_data.sql +420 -0
- package/entities/campaigns/campaigns.config.ts +61 -0
- package/entities/campaigns/campaigns.fields.ts +413 -0
- package/entities/campaigns/campaigns.service.ts +426 -0
- package/entities/campaigns/campaigns.types.ts +124 -0
- package/entities/campaigns/messages/en.json +145 -0
- package/entities/campaigns/messages/es.json +145 -0
- package/entities/campaigns/migrations/001_campaigns_table.sql +127 -0
- package/entities/campaigns/migrations/002_campaigns_metas.sql +114 -0
- package/entities/campaigns/migrations/003_campaigns_sample_data.sql +364 -0
- package/entities/companies/companies.config.ts +61 -0
- package/entities/companies/companies.fields.ts +429 -0
- package/entities/companies/companies.service.ts +566 -0
- package/entities/companies/companies.types.ts +125 -0
- package/entities/companies/messages/en.json +146 -0
- package/entities/companies/messages/es.json +146 -0
- package/entities/companies/migrations/001_companies_table.sql +150 -0
- package/entities/companies/migrations/002_companies_metas.sql +114 -0
- package/entities/companies/migrations/003_companies_sample_data.sql +246 -0
- package/entities/contacts/contacts.config.ts +61 -0
- package/entities/contacts/contacts.fields.ts +359 -0
- package/entities/contacts/contacts.service.ts +509 -0
- package/entities/contacts/contacts.types.ts +108 -0
- package/entities/contacts/messages/en.json +117 -0
- package/entities/contacts/messages/es.json +117 -0
- package/entities/contacts/migrations/001_contacts_table.sql +134 -0
- package/entities/contacts/migrations/002_contacts_metas.sql +114 -0
- package/entities/contacts/migrations/003_contacts_sample_data.sql +421 -0
- package/entities/leads/leads.config.ts +61 -0
- package/entities/leads/leads.fields.ts +336 -0
- package/entities/leads/leads.service.ts +496 -0
- package/entities/leads/leads.types.ts +114 -0
- package/entities/leads/messages/en.json +132 -0
- package/entities/leads/messages/es.json +132 -0
- package/entities/leads/migrations/001_leads_table.sql +150 -0
- package/entities/leads/migrations/002_leads_metas.sql +120 -0
- package/entities/leads/migrations/003_leads_sample_data.sql +242 -0
- package/entities/notes/messages/en.json +114 -0
- package/entities/notes/messages/es.json +114 -0
- package/entities/notes/migrations/020_notes_table.sql +118 -0
- package/entities/notes/migrations/021_notes_metas.sql +114 -0
- package/entities/notes/migrations/022_notes_sample_data.sql +275 -0
- package/entities/notes/notes.config.ts +61 -0
- package/entities/notes/notes.fields.ts +283 -0
- package/entities/notes/notes.service.ts +320 -0
- package/entities/notes/notes.types.ts +102 -0
- package/entities/opportunities/messages/en.json +107 -0
- package/entities/opportunities/messages/es.json +107 -0
- package/entities/opportunities/migrations/010_opportunities_table.sql +145 -0
- package/entities/opportunities/migrations/011_opportunities_metas.sql +114 -0
- package/entities/opportunities/migrations/012_opportunities_sample_data.sql +438 -0
- package/entities/opportunities/opportunities.config.ts +61 -0
- package/entities/opportunities/opportunities.fields.ts +416 -0
- package/entities/opportunities/opportunities.service.ts +525 -0
- package/entities/opportunities/opportunities.types.ts +135 -0
- package/entities/pipelines/messages/en.json +115 -0
- package/entities/pipelines/messages/es.json +115 -0
- package/entities/pipelines/migrations/001_pipelines_table.sql +106 -0
- package/entities/pipelines/migrations/002_pipelines_metas.sql +114 -0
- package/entities/pipelines/migrations/003_pipelines_sample_data.sql +91 -0
- package/entities/pipelines/pipelines.config.ts +62 -0
- package/entities/pipelines/pipelines.fields.ts +193 -0
- package/entities/pipelines/pipelines.service.ts +383 -0
- package/entities/pipelines/pipelines.types.ts +78 -0
- package/entities/products/messages/en.json +135 -0
- package/entities/products/messages/es.json +135 -0
- package/entities/products/migrations/001_products_table.sql +117 -0
- package/entities/products/migrations/002_products_metas.sql +114 -0
- package/entities/products/migrations/003_products_sample_data.sql +247 -0
- package/entities/products/products.config.ts +62 -0
- package/entities/products/products.fields.ts +361 -0
- package/entities/products/products.service.ts +437 -0
- package/entities/products/products.types.ts +125 -0
- package/lib/crm-constants.ts +77 -0
- package/lib/crm-utils.ts +185 -0
- package/lib/selectors.ts +333 -0
- package/messages/en.json +131 -0
- package/messages/es.json +131 -0
- package/migrations/999_theme_sample_data.sql +473 -0
- package/package.json +18 -0
- package/pendings.md +205 -0
- package/permissions-matrix.md +216 -0
- package/styles/components.css +414 -0
- package/styles/crm-theme.css +358 -0
- package/styles/globals.css +576 -0
- package/styles/variables.css +111 -0
- package/templates/dashboard/(main)/activities/components/ActivityCard.tsx +169 -0
- package/templates/dashboard/(main)/activities/components/ActivityTimeline.tsx +165 -0
- package/templates/dashboard/(main)/activities/page.tsx +297 -0
- package/templates/dashboard/(main)/campaigns/page.tsx +373 -0
- package/templates/dashboard/(main)/companies/page.tsx +296 -0
- package/templates/dashboard/(main)/contacts/page.tsx +347 -0
- package/templates/dashboard/(main)/layout.tsx +98 -0
- package/templates/dashboard/(main)/leads/page.tsx +335 -0
- package/templates/dashboard/(main)/opportunities/[id]/edit/page.tsx +95 -0
- package/templates/dashboard/(main)/opportunities/create/page.tsx +94 -0
- package/templates/dashboard/(main)/opportunities/page.tsx +350 -0
- package/templates/dashboard/(main)/pipelines/[id]/edit/page.tsx +95 -0
- package/templates/dashboard/(main)/pipelines/[id]/page.tsx +143 -0
- package/templates/dashboard/(main)/pipelines/create/page.tsx +94 -0
- package/templates/dashboard/(main)/pipelines/page.tsx +234 -0
- package/templates/dashboard/(main)/products/[id]/edit/page.tsx +97 -0
- package/templates/dashboard/(main)/products/[id]/page.tsx +509 -0
- package/templates/dashboard/(main)/products/create/page.tsx +96 -0
- package/templates/dashboard/(main)/products/page.tsx +308 -0
- package/templates/shared/ActionButtons.tsx +41 -0
- package/templates/shared/CRMDashboard.tsx +519 -0
- package/templates/shared/CRMDataTable.tsx +441 -0
- package/templates/shared/CRMMetricCard.tsx +76 -0
- package/templates/shared/CRMMobileNav.tsx +172 -0
- package/templates/shared/CRMSidebar.tsx +346 -0
- package/templates/shared/CRMTopBar.tsx +265 -0
- package/templates/shared/DealCard.tsx +123 -0
- package/templates/shared/EntityCard.tsx +58 -0
- package/templates/shared/OpportunityForm.tsx +649 -0
- package/templates/shared/PipelineForm.tsx +367 -0
- package/templates/shared/PipelineKanban.tsx +194 -0
- package/templates/shared/QuickFilters.tsx +47 -0
- package/templates/shared/StageColumn.tsx +175 -0
- package/templates/shared/StageSelect.tsx +177 -0
- package/templates/shared/StagesRepeater.tsx +317 -0
- package/templates/shared/index.ts +9 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRM Dashboard Component
|
|
3
|
+
* Professional dashboard with KPIs and Pipeline Summary
|
|
4
|
+
*
|
|
5
|
+
* Use this component in your dashboard page to display CRM metrics
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client'
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from 'react'
|
|
11
|
+
import Link from 'next/link'
|
|
12
|
+
import { Button } from '@nextsparkjs/core/components/ui/button'
|
|
13
|
+
import {
|
|
14
|
+
DollarSign,
|
|
15
|
+
Users,
|
|
16
|
+
UserPlus,
|
|
17
|
+
Target,
|
|
18
|
+
TrendingUp,
|
|
19
|
+
TrendingDown,
|
|
20
|
+
ArrowRight,
|
|
21
|
+
Calendar,
|
|
22
|
+
CheckCircle2,
|
|
23
|
+
Clock,
|
|
24
|
+
AlertCircle,
|
|
25
|
+
Flame,
|
|
26
|
+
Building2,
|
|
27
|
+
Activity,
|
|
28
|
+
BarChart3
|
|
29
|
+
} from 'lucide-react'
|
|
30
|
+
import { cn } from '@nextsparkjs/core/lib/utils'
|
|
31
|
+
|
|
32
|
+
// Types
|
|
33
|
+
interface KPIData {
|
|
34
|
+
totalRevenue: number
|
|
35
|
+
revenueTrend: number
|
|
36
|
+
totalDeals: number
|
|
37
|
+
dealsTrend: number
|
|
38
|
+
activeLeads: number
|
|
39
|
+
leadsTrend: number
|
|
40
|
+
conversionRate: number
|
|
41
|
+
conversionTrend: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface PipelineStage {
|
|
45
|
+
id: string
|
|
46
|
+
name: string
|
|
47
|
+
value: number
|
|
48
|
+
count: number
|
|
49
|
+
color: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ActivityItem {
|
|
53
|
+
id: string
|
|
54
|
+
type: 'call' | 'email' | 'meeting' | 'task'
|
|
55
|
+
title: string
|
|
56
|
+
time: string
|
|
57
|
+
status: 'scheduled' | 'completed' | 'overdue'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface Deal {
|
|
61
|
+
id: string
|
|
62
|
+
name: string
|
|
63
|
+
value: number
|
|
64
|
+
company?: string
|
|
65
|
+
probability: number
|
|
66
|
+
stage: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// KPI Card component
|
|
70
|
+
function KPICard({
|
|
71
|
+
title,
|
|
72
|
+
value,
|
|
73
|
+
trend,
|
|
74
|
+
icon: Icon,
|
|
75
|
+
iconBg,
|
|
76
|
+
prefix = '',
|
|
77
|
+
suffix = '',
|
|
78
|
+
}: {
|
|
79
|
+
title: string
|
|
80
|
+
value: number | string
|
|
81
|
+
trend?: number
|
|
82
|
+
icon: React.ElementType
|
|
83
|
+
iconBg: string
|
|
84
|
+
prefix?: string
|
|
85
|
+
suffix?: string
|
|
86
|
+
}) {
|
|
87
|
+
const isPositive = trend && trend > 0
|
|
88
|
+
const TrendIcon = isPositive ? TrendingUp : TrendingDown
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="bg-card border rounded-xl p-5 transition-all hover:shadow-md hover:border-primary/20">
|
|
92
|
+
<div className="flex items-start justify-between">
|
|
93
|
+
<div className={cn('w-11 h-11 rounded-xl flex items-center justify-center', iconBg)}>
|
|
94
|
+
<Icon className="w-5 h-5" />
|
|
95
|
+
</div>
|
|
96
|
+
{trend !== undefined && (
|
|
97
|
+
<div className={cn(
|
|
98
|
+
'flex items-center gap-1 text-sm font-medium px-2 py-0.5 rounded-full',
|
|
99
|
+
isPositive
|
|
100
|
+
? 'bg-emerald-500/10 text-emerald-600'
|
|
101
|
+
: 'bg-destructive/10 text-destructive'
|
|
102
|
+
)}>
|
|
103
|
+
<TrendIcon className="w-3.5 h-3.5" />
|
|
104
|
+
<span>{Math.abs(trend)}%</span>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
<div className="mt-4">
|
|
109
|
+
<p className="text-2xl font-bold text-foreground">
|
|
110
|
+
{prefix}{typeof value === 'number' ? value.toLocaleString() : value}{suffix}
|
|
111
|
+
</p>
|
|
112
|
+
<p className="text-sm text-muted-foreground mt-0.5">{title}</p>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Pipeline Funnel component
|
|
119
|
+
function PipelineFunnel({ stages }: { stages: PipelineStage[] }) {
|
|
120
|
+
const maxValue = Math.max(...stages.map(s => s.value), 1)
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div className="bg-card border rounded-xl p-5">
|
|
124
|
+
<div className="flex items-center justify-between mb-5">
|
|
125
|
+
<div>
|
|
126
|
+
<h3 className="font-semibold text-foreground">Pipeline Overview</h3>
|
|
127
|
+
<p className="text-sm text-muted-foreground">Current deal distribution</p>
|
|
128
|
+
</div>
|
|
129
|
+
<Link href="/dashboard/pipelines">
|
|
130
|
+
<Button variant="ghost" size="sm" className="gap-1">
|
|
131
|
+
View All
|
|
132
|
+
<ArrowRight className="w-4 h-4" />
|
|
133
|
+
</Button>
|
|
134
|
+
</Link>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div className="space-y-3">
|
|
138
|
+
{stages.map((stage) => (
|
|
139
|
+
<div key={stage.id} className="group">
|
|
140
|
+
<div className="flex items-center justify-between text-sm mb-1.5">
|
|
141
|
+
<span className="font-medium text-foreground">{stage.name}</span>
|
|
142
|
+
<span className="text-muted-foreground">
|
|
143
|
+
{stage.count} deals · ${stage.value.toLocaleString()}
|
|
144
|
+
</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="h-8 bg-muted/50 rounded-lg overflow-hidden relative">
|
|
147
|
+
<div
|
|
148
|
+
className={cn(
|
|
149
|
+
'h-full rounded-lg transition-all duration-500 ease-out',
|
|
150
|
+
stage.color
|
|
151
|
+
)}
|
|
152
|
+
style={{
|
|
153
|
+
width: `${Math.max((stage.value / maxValue) * 100, 5)}%`,
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
<div className="absolute inset-0 flex items-center px-3">
|
|
157
|
+
<span className="text-xs font-medium text-white drop-shadow-sm">
|
|
158
|
+
${stage.value.toLocaleString()}
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
{/* Summary */}
|
|
167
|
+
<div className="mt-5 pt-4 border-t border-border">
|
|
168
|
+
<div className="flex items-center justify-between">
|
|
169
|
+
<div>
|
|
170
|
+
<p className="text-sm text-muted-foreground">Total Pipeline Value</p>
|
|
171
|
+
<p className="text-xl font-bold text-foreground">
|
|
172
|
+
${stages.reduce((sum, s) => sum + s.value, 0).toLocaleString()}
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="text-right">
|
|
176
|
+
<p className="text-sm text-muted-foreground">Total Deals</p>
|
|
177
|
+
<p className="text-xl font-bold text-foreground">
|
|
178
|
+
{stages.reduce((sum, s) => sum + s.count, 0)}
|
|
179
|
+
</p>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Activity status icon
|
|
188
|
+
function ActivityStatusIcon({ status }: { status: ActivityItem['status'] }) {
|
|
189
|
+
if (status === 'completed') {
|
|
190
|
+
return <CheckCircle2 className="w-4 h-4 text-emerald-600" />
|
|
191
|
+
}
|
|
192
|
+
if (status === 'overdue') {
|
|
193
|
+
return <AlertCircle className="w-4 h-4 text-destructive" />
|
|
194
|
+
}
|
|
195
|
+
return <Clock className="w-4 h-4 text-amber-600" />
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Recent Activities component
|
|
199
|
+
function RecentActivities({ activities }: { activities: ActivityItem[] }) {
|
|
200
|
+
const typeIcons = {
|
|
201
|
+
call: '📞',
|
|
202
|
+
email: '✉️',
|
|
203
|
+
meeting: '📅',
|
|
204
|
+
task: '✓',
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="bg-card border rounded-xl p-5">
|
|
209
|
+
<div className="flex items-center justify-between mb-5">
|
|
210
|
+
<div>
|
|
211
|
+
<h3 className="font-semibold text-foreground">Upcoming Activities</h3>
|
|
212
|
+
<p className="text-sm text-muted-foreground">Your schedule for today</p>
|
|
213
|
+
</div>
|
|
214
|
+
<Link href="/dashboard/activities">
|
|
215
|
+
<Button variant="ghost" size="sm" className="gap-1">
|
|
216
|
+
View All
|
|
217
|
+
<ArrowRight className="w-4 h-4" />
|
|
218
|
+
</Button>
|
|
219
|
+
</Link>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<div className="space-y-3">
|
|
223
|
+
{activities.length > 0 ? (
|
|
224
|
+
activities.map((activity) => (
|
|
225
|
+
<div
|
|
226
|
+
key={activity.id}
|
|
227
|
+
className="flex items-center gap-3 p-3 rounded-lg bg-muted/30 hover:bg-muted/50 transition-colors cursor-pointer"
|
|
228
|
+
>
|
|
229
|
+
<div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center text-sm">
|
|
230
|
+
{typeIcons[activity.type]}
|
|
231
|
+
</div>
|
|
232
|
+
<div className="flex-1 min-w-0">
|
|
233
|
+
<p className="font-medium text-foreground text-sm truncate">
|
|
234
|
+
{activity.title}
|
|
235
|
+
</p>
|
|
236
|
+
<p className="text-xs text-muted-foreground">{activity.time}</p>
|
|
237
|
+
</div>
|
|
238
|
+
<ActivityStatusIcon status={activity.status} />
|
|
239
|
+
</div>
|
|
240
|
+
))
|
|
241
|
+
) : (
|
|
242
|
+
<div className="py-8 text-center">
|
|
243
|
+
<Calendar className="w-8 h-8 mx-auto text-muted-foreground mb-2" />
|
|
244
|
+
<p className="text-sm text-muted-foreground">No activities scheduled</p>
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Hot Deals component
|
|
253
|
+
function HotDeals({ deals }: { deals: Deal[] }) {
|
|
254
|
+
return (
|
|
255
|
+
<div className="bg-card border rounded-xl p-5">
|
|
256
|
+
<div className="flex items-center justify-between mb-5">
|
|
257
|
+
<div className="flex items-center gap-2">
|
|
258
|
+
<Flame className="w-5 h-5 text-destructive" />
|
|
259
|
+
<div>
|
|
260
|
+
<h3 className="font-semibold text-foreground">Hot Deals</h3>
|
|
261
|
+
<p className="text-sm text-muted-foreground">High-value opportunities</p>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
<Link href="/dashboard/pipelines">
|
|
265
|
+
<Button variant="ghost" size="sm" className="gap-1">
|
|
266
|
+
View All
|
|
267
|
+
<ArrowRight className="w-4 h-4" />
|
|
268
|
+
</Button>
|
|
269
|
+
</Link>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className="space-y-3">
|
|
273
|
+
{deals.length > 0 ? (
|
|
274
|
+
deals.map((deal) => (
|
|
275
|
+
<div
|
|
276
|
+
key={deal.id}
|
|
277
|
+
className="flex items-center gap-3 p-3 rounded-lg border border-border hover:border-primary/30 hover:shadow-sm transition-all cursor-pointer"
|
|
278
|
+
>
|
|
279
|
+
<div className="flex-1 min-w-0">
|
|
280
|
+
<p className="font-medium text-foreground text-sm truncate">
|
|
281
|
+
{deal.name}
|
|
282
|
+
</p>
|
|
283
|
+
<div className="flex items-center gap-2 mt-1">
|
|
284
|
+
{deal.company && (
|
|
285
|
+
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
286
|
+
<Building2 className="w-3 h-3" />
|
|
287
|
+
{deal.company}
|
|
288
|
+
</span>
|
|
289
|
+
)}
|
|
290
|
+
<span className="text-xs text-muted-foreground">
|
|
291
|
+
{deal.stage}
|
|
292
|
+
</span>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
<div className="text-right">
|
|
296
|
+
<p className="font-semibold text-primary text-sm">
|
|
297
|
+
${deal.value.toLocaleString()}
|
|
298
|
+
</p>
|
|
299
|
+
<p className="text-xs text-muted-foreground">
|
|
300
|
+
{deal.probability}% likely
|
|
301
|
+
</p>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
))
|
|
305
|
+
) : (
|
|
306
|
+
<div className="py-8 text-center">
|
|
307
|
+
<Target className="w-8 h-8 mx-auto text-muted-foreground mb-2" />
|
|
308
|
+
<p className="text-sm text-muted-foreground">No hot deals yet</p>
|
|
309
|
+
</div>
|
|
310
|
+
)}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Quick Stats Bar
|
|
317
|
+
function QuickStatsBar() {
|
|
318
|
+
return (
|
|
319
|
+
<div className="bg-gradient-to-r from-primary/5 via-primary/10 to-primary/5 border border-primary/10 rounded-xl p-4">
|
|
320
|
+
<div className="flex items-center justify-between flex-wrap gap-4">
|
|
321
|
+
<div className="flex items-center gap-3">
|
|
322
|
+
<div className="w-10 h-10 rounded-xl bg-primary/20 flex items-center justify-center">
|
|
323
|
+
<Activity className="w-5 h-5 text-primary" />
|
|
324
|
+
</div>
|
|
325
|
+
<div>
|
|
326
|
+
<p className="text-sm font-medium text-foreground">Welcome back!</p>
|
|
327
|
+
<p className="text-xs text-muted-foreground">Here's what's happening with your sales today.</p>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
<div className="flex items-center gap-3">
|
|
331
|
+
<Link href="/dashboard/leads/create">
|
|
332
|
+
<Button size="sm" variant="outline" className="gap-2">
|
|
333
|
+
<UserPlus className="w-4 h-4" />
|
|
334
|
+
Add Lead
|
|
335
|
+
</Button>
|
|
336
|
+
</Link>
|
|
337
|
+
<Link href="/dashboard/pipelines">
|
|
338
|
+
<Button size="sm" className="gap-2">
|
|
339
|
+
<BarChart3 className="w-4 h-4" />
|
|
340
|
+
View Pipeline
|
|
341
|
+
</Button>
|
|
342
|
+
</Link>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Sales Performance Chart
|
|
350
|
+
function SalesPerformanceChart() {
|
|
351
|
+
return (
|
|
352
|
+
<div className="bg-card border rounded-xl p-5">
|
|
353
|
+
<div className="flex items-center justify-between mb-5">
|
|
354
|
+
<div>
|
|
355
|
+
<h3 className="font-semibold text-foreground">Sales Performance</h3>
|
|
356
|
+
<p className="text-sm text-muted-foreground">Monthly revenue trend</p>
|
|
357
|
+
</div>
|
|
358
|
+
<Button variant="ghost" size="sm">
|
|
359
|
+
This Month
|
|
360
|
+
</Button>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
{/* Simple bar chart visualization */}
|
|
364
|
+
<div className="flex items-end gap-2 h-40 mt-4">
|
|
365
|
+
{['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'].map((month, i) => {
|
|
366
|
+
const heights = [45, 60, 55, 75, 85, 70]
|
|
367
|
+
return (
|
|
368
|
+
<div key={month} className="flex-1 flex flex-col items-center gap-2">
|
|
369
|
+
<div
|
|
370
|
+
className="w-full bg-primary/80 rounded-t-md transition-all hover:bg-primary"
|
|
371
|
+
style={{ height: `${heights[i]}%` }}
|
|
372
|
+
/>
|
|
373
|
+
<span className="text-xs text-muted-foreground">{month}</span>
|
|
374
|
+
</div>
|
|
375
|
+
)
|
|
376
|
+
})}
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
<div className="mt-4 pt-4 border-t border-border flex items-center justify-between">
|
|
380
|
+
<div>
|
|
381
|
+
<p className="text-sm text-muted-foreground">vs. Last Month</p>
|
|
382
|
+
<p className="text-lg font-semibold text-emerald-600">+18.2%</p>
|
|
383
|
+
</div>
|
|
384
|
+
<div className="text-right">
|
|
385
|
+
<p className="text-sm text-muted-foreground">Forecast</p>
|
|
386
|
+
<p className="text-lg font-semibold text-foreground">$312,000</p>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Main CRM Dashboard Component
|
|
394
|
+
export interface CRMDashboardProps {
|
|
395
|
+
kpis?: KPIData
|
|
396
|
+
pipelineStages?: PipelineStage[]
|
|
397
|
+
activities?: ActivityItem[]
|
|
398
|
+
hotDeals?: Deal[]
|
|
399
|
+
isLoading?: boolean
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function CRMDashboard({
|
|
403
|
+
kpis,
|
|
404
|
+
pipelineStages,
|
|
405
|
+
activities,
|
|
406
|
+
hotDeals,
|
|
407
|
+
isLoading = false,
|
|
408
|
+
}: CRMDashboardProps) {
|
|
409
|
+
// Default mock data if not provided
|
|
410
|
+
const defaultKPIs: KPIData = kpis || {
|
|
411
|
+
totalRevenue: 284500,
|
|
412
|
+
revenueTrend: 12.5,
|
|
413
|
+
totalDeals: 47,
|
|
414
|
+
dealsTrend: 8,
|
|
415
|
+
activeLeads: 156,
|
|
416
|
+
leadsTrend: -3,
|
|
417
|
+
conversionRate: 24,
|
|
418
|
+
conversionTrend: 5,
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const defaultPipelineStages: PipelineStage[] = pipelineStages || [
|
|
422
|
+
{ id: '1', name: 'Discovery', value: 125000, count: 18, color: 'bg-violet-500' },
|
|
423
|
+
{ id: '2', name: 'Qualification', value: 89000, count: 12, color: 'bg-primary' },
|
|
424
|
+
{ id: '3', name: 'Proposal', value: 67000, count: 8, color: 'bg-amber-500' },
|
|
425
|
+
{ id: '4', name: 'Negotiation', value: 45000, count: 5, color: 'bg-orange-500' },
|
|
426
|
+
{ id: '5', name: 'Closed Won', value: 284500, count: 4, color: 'bg-emerald-500' },
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
const defaultActivities: ActivityItem[] = activities || [
|
|
430
|
+
{ id: '1', type: 'call', title: 'Follow up with Acme Corp', time: '10:00 AM', status: 'scheduled' },
|
|
431
|
+
{ id: '2', type: 'meeting', title: 'Demo with Tech Solutions', time: '2:00 PM', status: 'scheduled' },
|
|
432
|
+
{ id: '3', type: 'email', title: 'Send proposal to GlobalTech', time: '4:00 PM', status: 'scheduled' },
|
|
433
|
+
{ id: '4', type: 'task', title: 'Update CRM records', time: 'Yesterday', status: 'overdue' },
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
const defaultHotDeals: Deal[] = hotDeals || [
|
|
437
|
+
{ id: '1', name: 'Enterprise License Deal', value: 85000, company: 'Acme Corp', probability: 85, stage: 'Negotiation' },
|
|
438
|
+
{ id: '2', name: 'Annual Subscription', value: 45000, company: 'Tech Solutions', probability: 75, stage: 'Proposal' },
|
|
439
|
+
{ id: '3', name: 'Consulting Package', value: 32000, company: 'GlobalTech', probability: 70, stage: 'Qualification' },
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
if (isLoading) {
|
|
443
|
+
return (
|
|
444
|
+
<div className="p-6 space-y-6">
|
|
445
|
+
<div className="h-16 bg-muted animate-pulse rounded-xl" />
|
|
446
|
+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
447
|
+
{[1, 2, 3, 4].map(i => (
|
|
448
|
+
<div key={i} className="h-32 bg-muted animate-pulse rounded-xl" />
|
|
449
|
+
))}
|
|
450
|
+
</div>
|
|
451
|
+
<div className="grid lg:grid-cols-2 gap-6">
|
|
452
|
+
<div className="h-80 bg-muted animate-pulse rounded-xl" />
|
|
453
|
+
<div className="h-80 bg-muted animate-pulse rounded-xl" />
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return (
|
|
460
|
+
<div className="p-6 space-y-6">
|
|
461
|
+
{/* Quick Stats Bar */}
|
|
462
|
+
<QuickStatsBar />
|
|
463
|
+
|
|
464
|
+
{/* KPI Cards */}
|
|
465
|
+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
466
|
+
<KPICard
|
|
467
|
+
title="Total Revenue"
|
|
468
|
+
value={defaultKPIs.totalRevenue}
|
|
469
|
+
trend={defaultKPIs.revenueTrend}
|
|
470
|
+
icon={DollarSign}
|
|
471
|
+
iconBg="bg-emerald-500/10 text-emerald-600"
|
|
472
|
+
prefix="$"
|
|
473
|
+
/>
|
|
474
|
+
<KPICard
|
|
475
|
+
title="Active Deals"
|
|
476
|
+
value={defaultKPIs.totalDeals}
|
|
477
|
+
trend={defaultKPIs.dealsTrend}
|
|
478
|
+
icon={Target}
|
|
479
|
+
iconBg="bg-primary/10 text-primary"
|
|
480
|
+
/>
|
|
481
|
+
<KPICard
|
|
482
|
+
title="Active Leads"
|
|
483
|
+
value={defaultKPIs.activeLeads}
|
|
484
|
+
trend={defaultKPIs.leadsTrend}
|
|
485
|
+
icon={Users}
|
|
486
|
+
iconBg="bg-amber-500/10 text-amber-600"
|
|
487
|
+
/>
|
|
488
|
+
<KPICard
|
|
489
|
+
title="Conversion Rate"
|
|
490
|
+
value={defaultKPIs.conversionRate}
|
|
491
|
+
trend={defaultKPIs.conversionTrend}
|
|
492
|
+
icon={TrendingUp}
|
|
493
|
+
iconBg="bg-violet-500/10 text-violet-600"
|
|
494
|
+
suffix="%"
|
|
495
|
+
/>
|
|
496
|
+
</div>
|
|
497
|
+
|
|
498
|
+
{/* Main Content Grid */}
|
|
499
|
+
<div className="grid lg:grid-cols-2 gap-6">
|
|
500
|
+
{/* Pipeline Funnel */}
|
|
501
|
+
<PipelineFunnel stages={defaultPipelineStages} />
|
|
502
|
+
|
|
503
|
+
{/* Hot Deals */}
|
|
504
|
+
<HotDeals deals={defaultHotDeals} />
|
|
505
|
+
</div>
|
|
506
|
+
|
|
507
|
+
{/* Bottom Section */}
|
|
508
|
+
<div className="grid lg:grid-cols-2 gap-6">
|
|
509
|
+
{/* Recent Activities */}
|
|
510
|
+
<RecentActivities activities={defaultActivities} />
|
|
511
|
+
|
|
512
|
+
{/* Performance Chart */}
|
|
513
|
+
<SalesPerformanceChart />
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export default CRMDashboard
|