@crmy/web 0.5.5 → 0.5.9
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/dist/assets/index-CskfWp8E.js +560 -0
- package/dist/assets/index-D763l57m.css +1 -0
- package/{index.html → dist/index.html} +2 -1
- package/package.json +4 -1
- package/postcss.config.js +0 -6
- package/src/App.tsx +0 -158
- package/src/api/client.ts +0 -82
- package/src/api/hooks.ts +0 -689
- package/src/components/CustomFields.tsx +0 -240
- package/src/components/NavLink.tsx +0 -28
- package/src/components/crm/AIFab.tsx +0 -37
- package/src/components/crm/AccountDrawer.tsx +0 -372
- package/src/components/crm/ActivityTimeline.tsx +0 -115
- package/src/components/crm/AssignmentDrawer.tsx +0 -396
- package/src/components/crm/BriefingPanel.tsx +0 -217
- package/src/components/crm/CommandPalette.tsx +0 -254
- package/src/components/crm/ContactAvatar.tsx +0 -49
- package/src/components/crm/ContactDrawer.tsx +0 -438
- package/src/components/crm/ContextPanel.tsx +0 -200
- package/src/components/crm/CrmWidgets.tsx +0 -417
- package/src/components/crm/DrawerShell.tsx +0 -77
- package/src/components/crm/ListToolbar.tsx +0 -252
- package/src/components/crm/OpportunityDrawer.tsx +0 -372
- package/src/components/crm/PaginationBar.tsx +0 -111
- package/src/components/crm/QuickAddDrawer.tsx +0 -652
- package/src/components/crm/ShortcutsOverlay.tsx +0 -65
- package/src/components/crm/UseCaseDrawer.tsx +0 -454
- package/src/components/layout/MobileNav.tsx +0 -49
- package/src/components/layout/Sidebar.tsx +0 -157
- package/src/components/layout/TopBar.tsx +0 -54
- package/src/components/settings/ActorsSettings.tsx +0 -1190
- package/src/components/ui/accordion.tsx +0 -52
- package/src/components/ui/alert-dialog.tsx +0 -104
- package/src/components/ui/alert.tsx +0 -43
- package/src/components/ui/aspect-ratio.tsx +0 -5
- package/src/components/ui/avatar.tsx +0 -38
- package/src/components/ui/badge.tsx +0 -29
- package/src/components/ui/breadcrumb.tsx +0 -90
- package/src/components/ui/button.tsx +0 -47
- package/src/components/ui/calendar.tsx +0 -54
- package/src/components/ui/card.tsx +0 -43
- package/src/components/ui/carousel.tsx +0 -224
- package/src/components/ui/chart.tsx +0 -303
- package/src/components/ui/checkbox.tsx +0 -26
- package/src/components/ui/collapsible.tsx +0 -9
- package/src/components/ui/command.tsx +0 -132
- package/src/components/ui/context-menu.tsx +0 -178
- package/src/components/ui/date-picker.tsx +0 -313
- package/src/components/ui/dialog.tsx +0 -95
- package/src/components/ui/drawer.tsx +0 -87
- package/src/components/ui/dropdown-menu.tsx +0 -179
- package/src/components/ui/form.tsx +0 -129
- package/src/components/ui/hover-card.tsx +0 -27
- package/src/components/ui/input-otp.tsx +0 -61
- package/src/components/ui/input.tsx +0 -22
- package/src/components/ui/label.tsx +0 -17
- package/src/components/ui/menubar.tsx +0 -207
- package/src/components/ui/navigation-menu.tsx +0 -120
- package/src/components/ui/pagination.tsx +0 -81
- package/src/components/ui/popover.tsx +0 -29
- package/src/components/ui/progress.tsx +0 -23
- package/src/components/ui/radio-group.tsx +0 -36
- package/src/components/ui/resizable.tsx +0 -37
- package/src/components/ui/scroll-area.tsx +0 -38
- package/src/components/ui/select.tsx +0 -143
- package/src/components/ui/separator.tsx +0 -20
- package/src/components/ui/sheet.tsx +0 -107
- package/src/components/ui/sidebar.tsx +0 -637
- package/src/components/ui/skeleton.tsx +0 -7
- package/src/components/ui/slider.tsx +0 -23
- package/src/components/ui/sonner.tsx +0 -24
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/table.tsx +0 -72
- package/src/components/ui/tabs.tsx +0 -53
- package/src/components/ui/textarea.tsx +0 -21
- package/src/components/ui/toast.tsx +0 -111
- package/src/components/ui/toaster.tsx +0 -24
- package/src/components/ui/toggle-group.tsx +0 -49
- package/src/components/ui/toggle.tsx +0 -37
- package/src/components/ui/tooltip.tsx +0 -28
- package/src/components/ui/use-toast.ts +0 -1
- package/src/components/ui/utils.ts +0 -9
- package/src/contexts/AgentSettingsContext.tsx +0 -24
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/hooks/use-toast.ts +0 -186
- package/src/hooks/useKeyboardShortcuts.ts +0 -95
- package/src/hooks/useTheme.ts +0 -24
- package/src/index.css +0 -245
- package/src/lib/entityColors.ts +0 -18
- package/src/lib/stageConfig.ts +0 -32
- package/src/lib/utils.ts +0 -6
- package/src/main.tsx +0 -25
- package/src/pages/Accounts.tsx +0 -205
- package/src/pages/Activities.tsx +0 -251
- package/src/pages/Agent.tsx +0 -237
- package/src/pages/AgentSettings.tsx +0 -544
- package/src/pages/Assignments.tsx +0 -750
- package/src/pages/Contacts.tsx +0 -200
- package/src/pages/Dashboard.tsx +0 -143
- package/src/pages/Inbox.tsx +0 -615
- package/src/pages/NotFound.tsx +0 -24
- package/src/pages/Opportunities.tsx +0 -386
- package/src/pages/SearchResults.tsx +0 -49
- package/src/pages/Settings.tsx +0 -1884
- package/src/pages/UseCases.tsx +0 -396
- package/src/pages/auth/Login.tsx +0 -261
- package/src/pages/hitl/HITL.tsx +0 -101
- package/src/store/appStore.ts +0 -103
- package/src/vite-env.d.ts +0 -14
- package/tailwind.config.js +0 -121
- package/tsconfig.json +0 -24
- package/vite.config.ts +0 -27
- /package/{public → dist}/android-chrome-192x192.png +0 -0
- /package/{public → dist}/android-chrome-512x512.png +0 -0
- /package/{public → dist}/apple-touch-icon.png +0 -0
- /package/{src/assets/crmy-logo.png → dist/assets/crmy-logo-DWN0xBPW.png} +0 -0
- /package/{public → dist}/favicon-16x16.png +0 -0
- /package/{public → dist}/favicon-32x32.png +0 -0
- /package/{public → dist}/favicon.ico +0 -0
- /package/{public → dist}/favicon.svg +0 -0
- /package/{public → dist}/site.webmanifest +0 -0
package/src/pages/Contacts.tsx
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 CRMy Contributors
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { useState, useMemo, useEffect } from 'react';
|
|
5
|
-
import { useNavigate } from 'react-router-dom';
|
|
6
|
-
import { ContactAvatar } from '@/components/crm/ContactAvatar';
|
|
7
|
-
import { TopBar } from '@/components/layout/TopBar';
|
|
8
|
-
import { useContacts } from '@/api/hooks';
|
|
9
|
-
import { useAppStore } from '@/store/appStore';
|
|
10
|
-
import { useAgentSettings } from '@/contexts/AgentSettingsContext';
|
|
11
|
-
import { StageBadge } from '@/components/crm/CrmWidgets';
|
|
12
|
-
import { ListToolbar, type FilterConfig, type SortOption } from '@/components/crm/ListToolbar';
|
|
13
|
-
import { motion } from 'framer-motion';
|
|
14
|
-
import { LayoutGrid, List, Sparkles, ChevronUp, ChevronDown } from 'lucide-react';
|
|
15
|
-
import { PaginationBar } from '@/components/crm/PaginationBar';
|
|
16
|
-
import { useIsMobile } from '@/hooks/use-mobile';
|
|
17
|
-
import { stageConfig } from '@/lib/stageConfig';
|
|
18
|
-
|
|
19
|
-
type ViewMode = 'table' | 'cards';
|
|
20
|
-
|
|
21
|
-
const filterConfigs: FilterConfig[] = [
|
|
22
|
-
{ key: 'lifecycle_stage', label: 'Stage', options: Object.entries(stageConfig).map(([k, v]) => ({ value: k, label: v.label })) },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const sortOptions: SortOption[] = [
|
|
26
|
-
{ key: 'name', label: 'Name' },
|
|
27
|
-
{ key: 'company', label: 'Company' },
|
|
28
|
-
{ key: 'created_at', label: 'Created' },
|
|
29
|
-
{ key: 'lifecycle_stage', label: 'Stage' },
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
-
type Contact = any;
|
|
34
|
-
|
|
35
|
-
function displayName(c: Contact): string {
|
|
36
|
-
const parts = [c.first_name, c.last_name].filter(Boolean);
|
|
37
|
-
if (parts.length > 0) return parts.join(' ');
|
|
38
|
-
return c.email || c.company_name || 'Unknown';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default function Contacts() {
|
|
42
|
-
const navigate = useNavigate();
|
|
43
|
-
const isMobile = useIsMobile();
|
|
44
|
-
const [view, setView] = useState<ViewMode>('table');
|
|
45
|
-
const effectiveView = isMobile ? 'cards' : view;
|
|
46
|
-
const { openDrawer, openQuickAdd, openAIWithContext } = useAppStore();
|
|
47
|
-
const { enabled: agentEnabled } = useAgentSettings();
|
|
48
|
-
const [search, setSearch] = useState('');
|
|
49
|
-
const [activeFilters, setActiveFilters] = useState<Record<string, string[]>>({});
|
|
50
|
-
const [sort, setSort] = useState<{ key: string; dir: 'asc' | 'desc' } | null>(null);
|
|
51
|
-
const [page, setPage] = useState(1);
|
|
52
|
-
const [pageSize, setPageSize] = useState(25);
|
|
53
|
-
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
-
const { data, isLoading } = useContacts({ q: search || undefined, limit: 200 }) as any;
|
|
56
|
-
const allContacts: Contact[] = data?.data ?? [];
|
|
57
|
-
|
|
58
|
-
const handleFilterChange = (key: string, values: string[]) => {
|
|
59
|
-
setActiveFilters(prev => { const next = { ...prev }; if (values.length === 0) delete next[key]; else next[key] = values; return next; });
|
|
60
|
-
};
|
|
61
|
-
const handleSortChange = (key: string) => {
|
|
62
|
-
setSort(prev => prev?.key === key ? { key, dir: prev.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'asc' });
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const filtered = useMemo(() => {
|
|
66
|
-
let result = [...allContacts];
|
|
67
|
-
if (activeFilters.lifecycle_stage?.length) result = result.filter(c => activeFilters.lifecycle_stage.includes(c.lifecycle_stage as string));
|
|
68
|
-
if (sort) {
|
|
69
|
-
result.sort((a, b) => {
|
|
70
|
-
const aVal = (a[sort.key] ?? '') as string | number;
|
|
71
|
-
const bVal = (b[sort.key] ?? '') as string | number;
|
|
72
|
-
if (typeof aVal === 'number' && typeof bVal === 'number') return sort.dir === 'asc' ? aVal - bVal : bVal - aVal;
|
|
73
|
-
return sort.dir === 'asc' ? String(aVal).localeCompare(String(bVal)) : String(bVal).localeCompare(String(aVal));
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
return result;
|
|
77
|
-
}, [allContacts, activeFilters, sort]);
|
|
78
|
-
|
|
79
|
-
useEffect(() => { setPage(1); }, [search, activeFilters, sort]);
|
|
80
|
-
const paginated = filtered.slice((page - 1) * pageSize, page * pageSize);
|
|
81
|
-
|
|
82
|
-
const SortHeader = ({ label, sortKey }: { label: string; sortKey: string }) => (
|
|
83
|
-
<th onClick={() => handleSortChange(sortKey)}
|
|
84
|
-
className="text-left px-4 py-3 text-xs font-display font-semibold text-muted-foreground cursor-pointer hover:text-foreground transition-colors select-none">
|
|
85
|
-
<span className="inline-flex items-center gap-1">
|
|
86
|
-
{label}
|
|
87
|
-
{sort?.key === sortKey ? (sort.dir === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />) : null}
|
|
88
|
-
</span>
|
|
89
|
-
</th>
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
<div className="flex flex-col h-full">
|
|
94
|
-
<TopBar title="Contacts">
|
|
95
|
-
<div className="hidden md:flex items-center gap-1 bg-muted rounded-xl p-0.5">
|
|
96
|
-
<button onClick={() => setView('table')} className={`p-1.5 rounded-lg text-sm transition-all ${view === 'table' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground'}`}>
|
|
97
|
-
<List className="w-4 h-4" />
|
|
98
|
-
</button>
|
|
99
|
-
<button onClick={() => setView('cards')} className={`p-1.5 rounded-lg text-sm transition-all ${view === 'cards' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground'}`}>
|
|
100
|
-
<LayoutGrid className="w-4 h-4" />
|
|
101
|
-
</button>
|
|
102
|
-
</div>
|
|
103
|
-
</TopBar>
|
|
104
|
-
|
|
105
|
-
<ListToolbar
|
|
106
|
-
searchValue={search} onSearchChange={setSearch} searchPlaceholder="Search contacts..."
|
|
107
|
-
filters={filterConfigs} activeFilters={activeFilters} onFilterChange={handleFilterChange}
|
|
108
|
-
onClearFilters={() => setActiveFilters({})} sortOptions={sortOptions} currentSort={sort}
|
|
109
|
-
onSortChange={handleSortChange} onAdd={() => openQuickAdd('contact')} addLabel="New Contact" entityType="contacts"
|
|
110
|
-
/>
|
|
111
|
-
|
|
112
|
-
<div className="flex-1 overflow-y-auto px-4 md:px-6 pb-24 md:pb-6">
|
|
113
|
-
{isLoading ? (
|
|
114
|
-
<div className="space-y-2 pt-2">
|
|
115
|
-
{[...Array(5)].map((_, i) => <div key={i} className="h-14 bg-muted/50 rounded-xl animate-pulse" />)}
|
|
116
|
-
</div>
|
|
117
|
-
) : filtered.length === 0 ? (
|
|
118
|
-
<div className="flex flex-col items-center justify-center py-16 text-muted-foreground">
|
|
119
|
-
<p className="text-sm">No contacts found.</p>
|
|
120
|
-
<button onClick={() => { setSearch(''); setActiveFilters({}); }} className="mt-2 text-xs text-primary font-semibold hover:underline">
|
|
121
|
-
Clear all filters
|
|
122
|
-
</button>
|
|
123
|
-
</div>
|
|
124
|
-
) : effectiveView === 'table' ? (
|
|
125
|
-
<div className="bg-card border border-border rounded-2xl overflow-hidden shadow-sm">
|
|
126
|
-
<div className="overflow-x-auto">
|
|
127
|
-
<table className="w-full text-sm">
|
|
128
|
-
<thead>
|
|
129
|
-
<tr className="border-b border-border bg-surface-sunken/50">
|
|
130
|
-
<SortHeader label="Name" sortKey="name" />
|
|
131
|
-
<SortHeader label="Company" sortKey="company" />
|
|
132
|
-
<th className="text-left px-4 py-3 text-xs font-display font-semibold text-muted-foreground">Phone</th>
|
|
133
|
-
<SortHeader label="Stage" sortKey="lifecycle_stage" />
|
|
134
|
-
{agentEnabled && <th className="px-2 py-3 w-8"></th>}
|
|
135
|
-
</tr>
|
|
136
|
-
</thead>
|
|
137
|
-
<tbody>
|
|
138
|
-
{paginated.map((c, i) => (
|
|
139
|
-
<tr key={c.id as string} onClick={() => openDrawer('contact', c.id as string)}
|
|
140
|
-
className={`border-b border-border last:border-0 hover:bg-primary/5 cursor-pointer group transition-colors ${i % 2 === 1 ? 'bg-surface-sunken/30' : ''}`}>
|
|
141
|
-
<td className="px-4 py-3">
|
|
142
|
-
<div className="flex items-center gap-3">
|
|
143
|
-
<ContactAvatar name={displayName(c)} className="w-8 h-8 text-xs" />
|
|
144
|
-
<span className="font-semibold text-foreground">{displayName(c)}</span>
|
|
145
|
-
</div>
|
|
146
|
-
</td>
|
|
147
|
-
<td className="px-4 py-3 text-muted-foreground">{(c.company_name as string) || '—'}</td>
|
|
148
|
-
<td className="px-4 py-3 text-muted-foreground text-xs">{(c.phone as string) || '—'}</td>
|
|
149
|
-
<td className="px-4 py-3">{c.lifecycle_stage ? <StageBadge stage={c.lifecycle_stage as string} /> : '—'}</td>
|
|
150
|
-
{agentEnabled && (
|
|
151
|
-
<td className="px-2 py-3">
|
|
152
|
-
<button onClick={(e) => { e.stopPropagation(); openAIWithContext({ type: 'contact', id: c.id as string, name: displayName(c), detail: c.company_name as string }); navigate('/agent'); }}
|
|
153
|
-
className="p-1.5 rounded-lg opacity-0 group-hover:opacity-100 hover:bg-accent/10 transition-all">
|
|
154
|
-
<Sparkles className="w-3.5 h-3.5 text-accent" />
|
|
155
|
-
</button>
|
|
156
|
-
</td>
|
|
157
|
-
)}
|
|
158
|
-
</tr>
|
|
159
|
-
))}
|
|
160
|
-
</tbody>
|
|
161
|
-
</table>
|
|
162
|
-
</div>
|
|
163
|
-
<div className="px-4">
|
|
164
|
-
<PaginationBar page={page} pageSize={pageSize} total={filtered.length} onPageChange={setPage} onPageSizeChange={setPageSize} />
|
|
165
|
-
</div>
|
|
166
|
-
</div>
|
|
167
|
-
) : (
|
|
168
|
-
<>
|
|
169
|
-
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
|
|
170
|
-
{paginated.map((c, i) => (
|
|
171
|
-
<motion.div key={c.id as string} initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.02 }}
|
|
172
|
-
onClick={() => openDrawer('contact', c.id as string)}
|
|
173
|
-
className="bg-card border border-border rounded-2xl p-4 cursor-pointer hover:shadow-lg hover:border-primary/20 transition-all press-scale group relative">
|
|
174
|
-
{agentEnabled && (
|
|
175
|
-
<button onClick={(e) => { e.stopPropagation(); openAIWithContext({ type: 'contact', id: c.id as string, name: displayName(c), detail: c.company_name as string }); navigate('/agent'); }}
|
|
176
|
-
className="absolute top-3 right-3 p-1.5 rounded-lg hover:bg-accent/10 transition-all md:opacity-0 md:group-hover:opacity-100">
|
|
177
|
-
<Sparkles className="w-3.5 h-3.5 text-accent" />
|
|
178
|
-
</button>
|
|
179
|
-
)}
|
|
180
|
-
<div className="flex items-center gap-3 mb-3">
|
|
181
|
-
<ContactAvatar name={displayName(c)} className="w-11 h-11 rounded-2xl text-sm" />
|
|
182
|
-
<div>
|
|
183
|
-
<p className="font-display font-bold text-foreground">{displayName(c)}</p>
|
|
184
|
-
<p className="text-xs text-muted-foreground">{(c.company_name as string) || 'Individual'}</p>
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
<div className="flex items-center gap-2 mb-2">
|
|
188
|
-
{c.lifecycle_stage && <StageBadge stage={c.lifecycle_stage as string} />}
|
|
189
|
-
</div>
|
|
190
|
-
{c.email && <p className="text-xs text-muted-foreground">{c.email as string}</p>}
|
|
191
|
-
</motion.div>
|
|
192
|
-
))}
|
|
193
|
-
</div>
|
|
194
|
-
<PaginationBar page={page} pageSize={pageSize} total={filtered.length} onPageChange={setPage} onPageSizeChange={setPageSize} />
|
|
195
|
-
</>
|
|
196
|
-
)}
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
}
|
package/src/pages/Dashboard.tsx
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 CRMy Contributors
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { useState } from 'react';
|
|
5
|
-
import { TopBar } from '@/components/layout/TopBar';
|
|
6
|
-
import { PipelineSnapshot, ActivityFeed, AccountHealth } from '@/components/crm/CrmWidgets';
|
|
7
|
-
import { useAppStore } from '@/store/appStore';
|
|
8
|
-
import { useOpportunities } from '@/api/hooks';
|
|
9
|
-
import { motion } from 'framer-motion';
|
|
10
|
-
import { ArrowRight, TrendingUp, UserPlus, FolderKanban, Activity, Briefcase } from 'lucide-react';
|
|
11
|
-
|
|
12
|
-
function greeting() {
|
|
13
|
-
const h = new Date().getHours();
|
|
14
|
-
if (h < 12) return 'Good morning';
|
|
15
|
-
if (h < 17) return 'Good afternoon';
|
|
16
|
-
return 'Good evening';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default function Dashboard() {
|
|
20
|
-
const { openDrawer, openQuickAdd } = useAppStore();
|
|
21
|
-
const [activityWindow, setActivityWindow] = useState<'today' | 'week'>('today');
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
-
const { data: oppsData } = useOpportunities({ limit: 10 }) as any;
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
-
const opps: any[] = oppsData?.data ?? [];
|
|
26
|
-
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
-
const hotDeals: any[] = opps
|
|
29
|
-
.filter((d: any) => d.stage === 'negotiation' || (d.stage === 'proposal' && d.probability > 50))
|
|
30
|
-
.slice(0, 3);
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<div className="flex flex-col h-full">
|
|
34
|
-
<TopBar title="Dashboard" />
|
|
35
|
-
<div className="flex-1 overflow-y-auto p-4 md:p-6 pb-24 md:pb-6">
|
|
36
|
-
{/* Greeting */}
|
|
37
|
-
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} className="mb-6">
|
|
38
|
-
<h1 className="text-2xl md:text-3xl font-display font-extrabold">
|
|
39
|
-
<span className="gradient-text">{greeting()}</span>
|
|
40
|
-
</h1>
|
|
41
|
-
<p className="text-sm text-muted-foreground mt-1">Here's what needs your attention today.</p>
|
|
42
|
-
</motion.div>
|
|
43
|
-
|
|
44
|
-
{/* Quick Actions */}
|
|
45
|
-
<motion.div
|
|
46
|
-
initial={{ opacity: 0, y: 12 }}
|
|
47
|
-
animate={{ opacity: 1, y: 0 }}
|
|
48
|
-
transition={{ delay: 0.05 }}
|
|
49
|
-
className="grid grid-cols-2 md:grid-cols-4 gap-2 md:gap-3 mb-6"
|
|
50
|
-
>
|
|
51
|
-
{[
|
|
52
|
-
{ icon: UserPlus, label: 'New Contact', gradient: 'from-primary/15 to-primary/5', color: 'text-primary', action: () => openQuickAdd('contact') },
|
|
53
|
-
{ icon: TrendingUp, label: 'New Opportunity', gradient: 'from-accent/15 to-accent/5', color: 'text-accent', action: () => openQuickAdd('opportunity') },
|
|
54
|
-
{ icon: FolderKanban, label: 'New Use Case', gradient: 'from-success/15 to-success/5', color: 'text-success', action: () => openQuickAdd('use-case') },
|
|
55
|
-
{ icon: Activity, label: 'Log Activity', gradient: 'from-warning/15 to-warning/5', color: 'text-warning', action: () => openQuickAdd('activity') },
|
|
56
|
-
].map((action) => (
|
|
57
|
-
<button
|
|
58
|
-
key={action.label}
|
|
59
|
-
onClick={action.action}
|
|
60
|
-
className={`flex items-center gap-3 p-3 md:p-4 rounded-2xl bg-gradient-to-br ${action.gradient} border border-border/50 hover:shadow-md transition-all press-scale`}
|
|
61
|
-
>
|
|
62
|
-
<action.icon className={`w-5 h-5 ${action.color}`} />
|
|
63
|
-
<span className="text-sm font-display font-semibold text-foreground">{action.label}</span>
|
|
64
|
-
</button>
|
|
65
|
-
))}
|
|
66
|
-
</motion.div>
|
|
67
|
-
|
|
68
|
-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-6">
|
|
69
|
-
{/* Left column */}
|
|
70
|
-
<div className="lg:col-span-2 space-y-4 md:space-y-5">
|
|
71
|
-
{hotDeals.length > 0 && (
|
|
72
|
-
<motion.div
|
|
73
|
-
initial={{ opacity: 0, y: 12 }}
|
|
74
|
-
animate={{ opacity: 1, y: 0 }}
|
|
75
|
-
transition={{ delay: 0.1 }}
|
|
76
|
-
className="bg-card border border-border rounded-2xl p-5 shadow-sm"
|
|
77
|
-
>
|
|
78
|
-
<h2 className="font-display font-bold text-foreground mb-4">Today's focus</h2>
|
|
79
|
-
<div className="space-y-2">
|
|
80
|
-
{hotDeals.map((deal: Record<string, unknown>) => {
|
|
81
|
-
const contactName = (deal.contact_name ?? deal.contactName ?? '') as string;
|
|
82
|
-
const amount = (deal.amount as number) ?? 0;
|
|
83
|
-
return (
|
|
84
|
-
<div
|
|
85
|
-
key={deal.id as string}
|
|
86
|
-
onClick={() => openDrawer('opportunity', deal.id as string)}
|
|
87
|
-
className="flex items-center gap-3 p-3 rounded-xl bg-surface hover:bg-surface-sunken cursor-pointer transition-all press-scale"
|
|
88
|
-
>
|
|
89
|
-
<div className="w-8 h-8 rounded-xl bg-accent/10 flex items-center justify-center flex-shrink-0">
|
|
90
|
-
<Briefcase className="w-4 h-4 text-accent" />
|
|
91
|
-
</div>
|
|
92
|
-
<div className="flex-1 min-w-0">
|
|
93
|
-
<p className="text-sm font-semibold text-foreground truncate">{deal.name as string}</p>
|
|
94
|
-
<p className="text-xs text-muted-foreground">
|
|
95
|
-
${amount >= 1000 ? `${(amount / 1000).toFixed(0)}K` : amount} · {contactName}
|
|
96
|
-
</p>
|
|
97
|
-
</div>
|
|
98
|
-
<ArrowRight className="w-4 h-4 text-muted-foreground" />
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
})}
|
|
102
|
-
</div>
|
|
103
|
-
</motion.div>
|
|
104
|
-
)}
|
|
105
|
-
|
|
106
|
-
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.15 }}>
|
|
107
|
-
<div className="bg-card border border-border rounded-2xl p-5 shadow-sm">
|
|
108
|
-
<div className="flex items-center justify-between mb-3">
|
|
109
|
-
<h3 className="font-display font-bold text-foreground">Recent activity</h3>
|
|
110
|
-
<div className="flex items-center gap-0.5 bg-muted rounded-lg p-0.5">
|
|
111
|
-
<button
|
|
112
|
-
onClick={() => setActivityWindow('today')}
|
|
113
|
-
className={`px-2.5 py-1 rounded-md text-[11px] font-semibold transition-all ${activityWindow === 'today' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}
|
|
114
|
-
>
|
|
115
|
-
Today
|
|
116
|
-
</button>
|
|
117
|
-
<button
|
|
118
|
-
onClick={() => setActivityWindow('week')}
|
|
119
|
-
className={`px-2.5 py-1 rounded-md text-[11px] font-semibold transition-all ${activityWindow === 'week' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}
|
|
120
|
-
>
|
|
121
|
-
This Week
|
|
122
|
-
</button>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
<ActivityFeed limit={8} filterWindow={activityWindow} />
|
|
126
|
-
</div>
|
|
127
|
-
</motion.div>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
{/* Right column */}
|
|
131
|
-
<div className="space-y-4 md:space-y-5">
|
|
132
|
-
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.08 }}>
|
|
133
|
-
<PipelineSnapshot />
|
|
134
|
-
</motion.div>
|
|
135
|
-
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.11 }}>
|
|
136
|
-
<AccountHealth />
|
|
137
|
-
</motion.div>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|