create-crm-tmp 2.0.0 → 2.1.0

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 (190) hide show
  1. package/bin/create-crm-tmp.js +56 -35
  2. package/package.json +1 -1
  3. package/template/README.md +230 -115
  4. package/template/eslint.config.mjs +13 -0
  5. package/template/next.config.ts +14 -0
  6. package/template/package.json +15 -2
  7. package/template/prisma/migrations/20260318095700_init_db/migration.sql +978 -0
  8. package/template/prisma/migrations/migration_lock.toml +3 -0
  9. package/template/prisma/schema.prisma +132 -637
  10. package/template/src/app/(auth)/invite/[token]/page.tsx +10 -8
  11. package/template/src/app/(auth)/layout.tsx +1 -1
  12. package/template/src/app/(auth)/reset-password/complete/page.tsx +11 -8
  13. package/template/src/app/(auth)/reset-password/page.tsx +4 -4
  14. package/template/src/app/(auth)/reset-password/verify/page.tsx +4 -4
  15. package/template/src/app/(auth)/signin/page.tsx +14 -6
  16. package/template/src/app/(dashboard)/agenda/page.tsx +2243 -988
  17. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +18 -104
  18. package/template/src/app/(dashboard)/automatisation/page.tsx +10 -26
  19. package/template/src/app/(dashboard)/closing/page.tsx +78 -62
  20. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +2082 -1080
  21. package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +46 -47
  22. package/template/src/app/(dashboard)/contacts/page.tsx +1062 -780
  23. package/template/src/app/(dashboard)/dashboard/page.tsx +533 -37
  24. package/template/src/app/(dashboard)/dev/page.tsx +1291 -0
  25. package/template/src/app/(dashboard)/layout.tsx +6 -2
  26. package/template/src/app/(dashboard)/settings/page.tsx +797 -2582
  27. package/template/src/app/(dashboard)/templates/page.tsx +55 -54
  28. package/template/src/app/(dashboard)/users/list/page.tsx +51 -48
  29. package/template/src/app/(dashboard)/users/page.tsx +1 -1
  30. package/template/src/app/(dashboard)/users/permissions/page.tsx +2 -2
  31. package/template/src/app/(dashboard)/users/roles/page.tsx +7 -5
  32. package/template/src/app/api/agenda/google-events/route.ts +92 -0
  33. package/template/src/app/api/auth/check-active/route.ts +3 -2
  34. package/template/src/app/api/auth/google/route.ts +2 -1
  35. package/template/src/app/api/auth/google/status/route.ts +7 -31
  36. package/template/src/app/api/companies/[id]/activities/route.ts +1 -3
  37. package/template/src/app/api/companies/[id]/route.ts +1 -2
  38. package/template/src/app/api/companies/route.ts +42 -12
  39. package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +9 -31
  40. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +14 -32
  41. package/template/src/app/api/contacts/[id]/files/route.ts +112 -212
  42. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +27 -1
  43. package/template/src/app/api/contacts/[id]/interactions/route.ts +16 -16
  44. package/template/src/app/api/contacts/[id]/kyc/route.ts +21 -11
  45. package/template/src/app/api/contacts/[id]/meet/route.ts +19 -2
  46. package/template/src/app/api/contacts/[id]/route.ts +106 -34
  47. package/template/src/app/api/contacts/[id]/send-email/route.ts +27 -11
  48. package/template/src/app/api/contacts/[id]/workflows/run/route.ts +6 -0
  49. package/template/src/app/api/contacts/export/route.ts +9 -13
  50. package/template/src/app/api/contacts/import/route.ts +55 -25
  51. package/template/src/app/api/contacts/import-preview/route.ts +1 -1
  52. package/template/src/app/api/contacts/origins/route.ts +63 -0
  53. package/template/src/app/api/contacts/route.ts +153 -41
  54. package/template/src/app/api/cron/cleanup-editor-images/route.ts +166 -0
  55. package/template/src/app/api/dashboard/widgets/[id]/route.ts +44 -0
  56. package/template/src/app/api/dashboard/widgets/route.ts +181 -0
  57. package/template/src/app/api/dev/reminders/test/route.ts +114 -0
  58. package/template/src/app/api/editor/upload-image/route.ts +61 -0
  59. package/template/src/app/api/integrations/google-sheet/jobs/[jobId]/route.ts +47 -0
  60. package/template/src/app/api/integrations/google-sheet/jobs/usage/route.ts +50 -0
  61. package/template/src/app/api/integrations/google-sheet/sync/route.ts +24 -556
  62. package/template/src/app/api/jobs/google-sheet/process/route.ts +84 -0
  63. package/template/src/app/api/jobs/google-sheet/schedule/route.ts +50 -0
  64. package/template/src/app/api/reminders/clear/route.ts +120 -0
  65. package/template/src/app/api/reminders/clear/undo/route.ts +112 -0
  66. package/template/src/app/api/reminders/route.ts +164 -39
  67. package/template/src/app/api/reminders/state/route.ts +164 -0
  68. package/template/src/app/api/reset-password/request/route.ts +1 -1
  69. package/template/src/app/api/reset-password/verify/route.ts +1 -1
  70. package/template/src/app/api/send/route.ts +16 -4
  71. package/template/src/app/api/settings/google-ads/route.ts +14 -0
  72. package/template/src/app/api/settings/google-calendar/calendars/route.ts +97 -0
  73. package/template/src/app/api/settings/google-calendar/route.ts +124 -0
  74. package/template/src/app/api/settings/google-sheet/[id]/route.ts +28 -0
  75. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +37 -4
  76. package/template/src/app/api/settings/google-sheet/preview/route.ts +9 -3
  77. package/template/src/app/api/settings/google-sheet/route.ts +14 -0
  78. package/template/src/app/api/settings/integrations/logs/route.ts +93 -0
  79. package/template/src/app/api/settings/integrations/notifications/route.ts +67 -0
  80. package/template/src/app/api/settings/meta-leads/[id]/route.ts +0 -1
  81. package/template/src/app/api/settings/meta-leads/route.ts +14 -2
  82. package/template/src/app/api/settings/smtp/route.ts +53 -6
  83. package/template/src/app/api/tasks/[id]/attendees/route.ts +24 -8
  84. package/template/src/app/api/tasks/[id]/route.ts +234 -58
  85. package/template/src/app/api/tasks/meet/route.ts +27 -19
  86. package/template/src/app/api/tasks/route.ts +62 -17
  87. package/template/src/app/api/users/[id]/route.ts +20 -14
  88. package/template/src/app/api/users/list/route.ts +57 -19
  89. package/template/src/app/api/webhooks/google-ads/route.ts +34 -14
  90. package/template/src/app/api/webhooks/meta-leads/route.ts +32 -12
  91. package/template/src/app/api/workflows/[id]/route.ts +0 -4
  92. package/template/src/app/api/workflows/process/route.ts +22 -51
  93. package/template/src/app/api/workflows/route.ts +0 -4
  94. package/template/src/app/globals.css +342 -4
  95. package/template/src/app/layout.tsx +11 -3
  96. package/template/src/app/page.tsx +1 -1
  97. package/template/src/components/address-autocomplete.tsx +7 -6
  98. package/template/src/components/config-error-alert.tsx +46 -0
  99. package/template/src/components/contacts/filter-bar.tsx +12 -3
  100. package/template/src/components/contacts/filter-builder.tsx +28 -43
  101. package/template/src/components/contacts/save-view-dialog.tsx +1 -1
  102. package/template/src/components/contacts/views-tab-bar.tsx +15 -6
  103. package/template/src/components/dashboard/activity-chart.tsx +41 -28
  104. package/template/src/components/dashboard/add-widget-dialog.tsx +157 -0
  105. package/template/src/components/dashboard/color-picker.tsx +64 -0
  106. package/template/src/components/dashboard/contacts-chart.tsx +69 -0
  107. package/template/src/components/dashboard/interactions-by-type-chart.tsx +121 -0
  108. package/template/src/components/dashboard/recent-activity.tsx +154 -0
  109. package/template/src/components/dashboard/stat-card.tsx +40 -40
  110. package/template/src/components/dashboard/status-distribution-chart.tsx +81 -0
  111. package/template/src/components/dashboard/tasks-pie-chart.tsx +37 -34
  112. package/template/src/components/dashboard/top-contacts-list.tsx +113 -0
  113. package/template/src/components/dashboard/upcoming-tasks-list.tsx +72 -81
  114. package/template/src/components/dashboard/widget-wrapper.tsx +36 -0
  115. package/template/src/components/date-picker.tsx +9 -6
  116. package/template/src/components/editor/upload-editor-image.ts +42 -0
  117. package/template/src/components/editor.tsx +161 -22
  118. package/template/src/components/email-template.tsx +2 -2
  119. package/template/src/components/global-search.tsx +30 -28
  120. package/template/src/components/header.tsx +178 -80
  121. package/template/src/components/inactive-account-guard.tsx +58 -0
  122. package/template/src/components/integration-notifications-listener.tsx +12 -0
  123. package/template/src/components/invitation-email-template.tsx +2 -2
  124. package/template/src/components/meet-cancellation-email-template.tsx +3 -3
  125. package/template/src/components/meet-confirmation-email-template.tsx +3 -3
  126. package/template/src/components/meet-update-email-template.tsx +3 -3
  127. package/template/src/components/page-header.tsx +5 -5
  128. package/template/src/components/protected-page.tsx +1 -1
  129. package/template/src/components/reset-password-email-template.tsx +2 -2
  130. package/template/src/components/settings/integrations/GoogleAdsIntegration.tsx +428 -0
  131. package/template/src/components/settings/integrations/GoogleSheetConfigMonitoringModal.tsx +680 -0
  132. package/template/src/components/settings/integrations/GoogleSheetIntegration.tsx +809 -0
  133. package/template/src/components/settings/integrations/ImportResultDialog.tsx +124 -0
  134. package/template/src/components/settings/integrations/IntegrationLogPanel.tsx +57 -0
  135. package/template/src/components/settings/integrations/IntegrationLogsTable.tsx +186 -0
  136. package/template/src/components/settings/integrations/MetaLeadIntegration.tsx +451 -0
  137. package/template/src/components/sidebar.tsx +45 -26
  138. package/template/src/components/skeleton.tsx +40 -43
  139. package/template/src/components/ui/accordion.tsx +2 -2
  140. package/template/src/components/ui/alert-dialog.tsx +1 -1
  141. package/template/src/components/ui/button.tsx +20 -9
  142. package/template/src/components/ui/components.tsx +1 -1
  143. package/template/src/components/ui/date-picker.tsx +422 -0
  144. package/template/src/components/ui/datetime-picker.tsx +338 -0
  145. package/template/src/components/ui/status-select.tsx +271 -0
  146. package/template/src/components/ui/tooltip.tsx +37 -0
  147. package/template/src/components/view-as-modal.tsx +13 -7
  148. package/template/src/contexts/app-toast-context.tsx +245 -57
  149. package/template/src/contexts/dashboard-theme-context.tsx +53 -0
  150. package/template/src/contexts/sidebar-context.tsx +22 -17
  151. package/template/src/contexts/task-reminder-context.tsx +134 -160
  152. package/template/src/contexts/view-as-context.tsx +33 -6
  153. package/template/src/hooks/use-focus-trap.ts +2 -2
  154. package/template/src/hooks/useIntegrationNotifications.ts +49 -0
  155. package/template/src/lib/auth.ts +8 -1
  156. package/template/src/lib/config-links.ts +14 -0
  157. package/template/src/lib/contact-duplicate.ts +79 -61
  158. package/template/src/lib/contact-interactions.ts +21 -21
  159. package/template/src/lib/contact-view-filters.ts +24 -64
  160. package/template/src/lib/contacts-list-url.ts +190 -0
  161. package/template/src/lib/dashboard-stats.ts +65 -7
  162. package/template/src/lib/dashboard-themes.ts +135 -0
  163. package/template/src/lib/date-utils.ts +127 -0
  164. package/template/src/lib/default-widgets.ts +12 -0
  165. package/template/src/lib/editor-html-image-dimensions.ts +172 -0
  166. package/template/src/lib/editor-image-limits.ts +19 -0
  167. package/template/src/lib/email-html-sanitize.ts +19 -0
  168. package/template/src/lib/encryption.ts +9 -6
  169. package/template/src/lib/fr-geography.ts +192 -0
  170. package/template/src/lib/google-calendar-agenda.ts +201 -0
  171. package/template/src/lib/google-calendar.ts +255 -5
  172. package/template/src/lib/google-sheet-sync-jobs.ts +96 -0
  173. package/template/src/lib/google-sheet-sync-runner.ts +514 -0
  174. package/template/src/lib/integration-import-log.ts +21 -0
  175. package/template/src/lib/permissions.ts +40 -10
  176. package/template/src/lib/prisma.ts +4 -1
  177. package/template/src/lib/qstash.ts +65 -0
  178. package/template/src/lib/reminder-state-server.ts +80 -0
  179. package/template/src/lib/reminder-state.ts +29 -0
  180. package/template/src/lib/supabase-storage.ts +113 -0
  181. package/template/src/lib/template-variables.ts +164 -23
  182. package/template/src/lib/utils.ts +45 -0
  183. package/template/src/lib/widget-registry.ts +173 -0
  184. package/template/src/lib/workflow-executor.ts +16 -70
  185. package/template/src/proxy.ts +1 -0
  186. package/template/vercel.json +3 -10
  187. package/template/skills-lock.json +0 -25
  188. package/template/src/components/dashboard/dashboard-content.tsx +0 -79
  189. package/template/src/lib/google-drive.ts +0 -1101
  190. package/template/src/types/yousign.ts +0 -52
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Area,
5
+ AreaChart,
6
+ ResponsiveContainer,
7
+ Tooltip,
8
+ XAxis,
9
+ YAxis,
10
+ CartesianGrid,
11
+ } from 'recharts';
12
+ import { useDashboardTheme } from '@/contexts/dashboard-theme-context';
13
+
14
+ interface ContactsChartProps {
15
+ readonly data: Array<{ month: string; count: number }>;
16
+ }
17
+
18
+ export function ContactsChart({ data }: Readonly<ContactsChartProps>) {
19
+ const { theme } = useDashboardTheme();
20
+
21
+ return (
22
+ <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
23
+ <div className="mb-4">
24
+ <h3 className="text-base font-semibold text-gray-900">Évolution des Contacts</h3>
25
+ <p className="mt-0.5 text-xs text-gray-400">Contacts créés par mois</p>
26
+ </div>
27
+ <div className="min-h-0 flex-1">
28
+ <ResponsiveContainer width="100%" height="100%">
29
+ <AreaChart data={data} margin={{ top: 5, right: 5, left: -20, bottom: 0 }}>
30
+ <defs>
31
+ <linearGradient id="colorContactsAccent" x1="0" y1="0" x2="0" y2="1">
32
+ <stop offset="0%" stopColor={theme.hex[500]} stopOpacity={0.3} />
33
+ <stop offset="95%" stopColor={theme.hex[500]} stopOpacity={0.02} />
34
+ </linearGradient>
35
+ </defs>
36
+ <CartesianGrid strokeDasharray="3 3" stroke="#f3f4f6" vertical={false} />
37
+ <XAxis
38
+ dataKey="month"
39
+ stroke="#d1d5db"
40
+ fontSize={11}
41
+ tickLine={false}
42
+ axisLine={false}
43
+ dy={8}
44
+ />
45
+ <YAxis stroke="#d1d5db" fontSize={11} tickLine={false} axisLine={false} width={40} />
46
+ <Tooltip
47
+ contentStyle={{
48
+ backgroundColor: '#fff',
49
+ border: '1px solid #f3f4f6',
50
+ borderRadius: '12px',
51
+ boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
52
+ fontSize: '13px',
53
+ }}
54
+ labelStyle={{ color: '#374151', fontWeight: 600 }}
55
+ />
56
+ <Area
57
+ type="monotone"
58
+ dataKey="count"
59
+ stroke={theme.hex[500]}
60
+ strokeWidth={2.5}
61
+ fill="url(#colorContactsAccent)"
62
+ name="Contacts"
63
+ />
64
+ </AreaChart>
65
+ </ResponsiveContainer>
66
+ </div>
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,121 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Bar,
5
+ BarChart,
6
+ ResponsiveContainer,
7
+ Tooltip,
8
+ XAxis,
9
+ YAxis,
10
+ CartesianGrid,
11
+ Cell,
12
+ } from 'recharts';
13
+
14
+ interface InteractionsByTypeChartProps {
15
+ readonly data: Array<{ type: string; count: number }>;
16
+ }
17
+
18
+ const TYPE_LABELS: Record<string, string> = {
19
+ CALL: 'Appels',
20
+ SMS: 'SMS',
21
+ EMAIL: 'Emails',
22
+ MEETING: 'Réunions',
23
+ NOTE: 'Notes',
24
+ TASK: 'Tâches',
25
+ STATUS_CHANGE: 'Changements',
26
+ APPOINTMENT_CREATED: 'RDV créés',
27
+ APPOINTMENT_DELETED: 'RDV supprimés',
28
+ APPOINTMENT_CHANGED: 'RDV modifiés',
29
+ ASSIGNMENT_CHANGE: 'Assignations',
30
+ CONTACT_UPDATE: 'Mises à jour',
31
+ FILE_UPLOADED: 'Fichiers',
32
+ FILE_REPLACED: 'Fichiers remplacés',
33
+ FILE_DELETED: 'Fichiers supprimés',
34
+ };
35
+
36
+ const TYPE_COLORS: Record<string, string> = {
37
+ CALL: '#3b82f6',
38
+ SMS: '#10b981',
39
+ EMAIL: '#f97316',
40
+ MEETING: '#8b5cf6',
41
+ NOTE: '#6b7280',
42
+ TASK: '#f59e0b',
43
+ STATUS_CHANGE: '#ef4444',
44
+ APPOINTMENT_CREATED: '#14b8a6',
45
+ APPOINTMENT_DELETED: '#ef4444',
46
+ APPOINTMENT_CHANGED: '#f97316',
47
+ ASSIGNMENT_CHANGE: '#ec4899',
48
+ CONTACT_UPDATE: '#06b6d4',
49
+ FILE_UPLOADED: '#84cc16',
50
+ FILE_REPLACED: '#a855f7',
51
+ FILE_DELETED: '#dc2626',
52
+ };
53
+
54
+ export function InteractionsByTypeChart({ data }: Readonly<InteractionsByTypeChartProps>) {
55
+ const chartData = data
56
+ .filter((d) => d.count > 0)
57
+ .map((d) => ({
58
+ ...d,
59
+ label: TYPE_LABELS[d.type] || d.type,
60
+ fill: TYPE_COLORS[d.type] || '#f97316',
61
+ }))
62
+ .sort((a, b) => b.count - a.count);
63
+
64
+ return (
65
+ <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
66
+ <div className="mb-4">
67
+ <h3 className="text-base font-semibold text-gray-900">Interactions par Type</h3>
68
+ <p className="mt-0.5 text-xs text-gray-400">Répartition des interactions ce mois</p>
69
+ </div>
70
+
71
+ {chartData.length === 0 ? (
72
+ <div className="flex flex-1 items-center justify-center">
73
+ <p className="text-sm text-gray-400">Aucune interaction ce mois</p>
74
+ </div>
75
+ ) : (
76
+ <div className="min-h-0 flex-1">
77
+ <ResponsiveContainer width="100%" height="100%">
78
+ <BarChart
79
+ data={chartData}
80
+ layout="vertical"
81
+ margin={{ top: 0, right: 20, left: 0, bottom: 0 }}
82
+ >
83
+ <CartesianGrid strokeDasharray="3 3" stroke="#f3f4f6" horizontal={false} />
84
+ <XAxis
85
+ type="number"
86
+ stroke="#d1d5db"
87
+ fontSize={11}
88
+ tickLine={false}
89
+ axisLine={false}
90
+ />
91
+ <YAxis
92
+ dataKey="label"
93
+ type="category"
94
+ stroke="#d1d5db"
95
+ fontSize={11}
96
+ tickLine={false}
97
+ axisLine={false}
98
+ width={80}
99
+ />
100
+ <Tooltip
101
+ contentStyle={{
102
+ backgroundColor: '#fff',
103
+ border: '1px solid #f3f4f6',
104
+ borderRadius: '12px',
105
+ boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
106
+ fontSize: '13px',
107
+ }}
108
+ formatter={(value: number) => [value, 'Interactions']}
109
+ />
110
+ <Bar dataKey="count" radius={[0, 6, 6, 0]} barSize={16}>
111
+ {chartData.map((entry) => (
112
+ <Cell key={`type-${entry.label}`} fill={entry.fill} />
113
+ ))}
114
+ </Bar>
115
+ </BarChart>
116
+ </ResponsiveContainer>
117
+ </div>
118
+ )}
119
+ </div>
120
+ );
121
+ }
@@ -0,0 +1,154 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import {
5
+ Phone,
6
+ Mail,
7
+ Calendar,
8
+ MessageSquare,
9
+ FileText,
10
+ TrendingUp,
11
+ RefreshCw,
12
+ CalendarCheck,
13
+ CalendarX,
14
+ CalendarClock,
15
+ UserCheck,
16
+ } from 'lucide-react';
17
+ import { cn } from '@/lib/utils';
18
+
19
+ interface Interaction {
20
+ id: string;
21
+ type: string;
22
+ title: string | null;
23
+ content: string;
24
+ date: string;
25
+ contact: {
26
+ id: string;
27
+ name: string;
28
+ };
29
+ }
30
+
31
+ interface RecentActivityProps {
32
+ readonly interactions: Interaction[];
33
+ }
34
+
35
+ const interactionIcons: Record<string, typeof Phone> = {
36
+ CALL: Phone,
37
+ SMS: MessageSquare,
38
+ EMAIL: Mail,
39
+ MEETING: Calendar,
40
+ NOTE: FileText,
41
+ STATUS_CHANGE: TrendingUp,
42
+ CONTACT_UPDATE: RefreshCw,
43
+ APPOINTMENT_CREATED: CalendarCheck,
44
+ APPOINTMENT_DELETED: CalendarX,
45
+ APPOINTMENT_CHANGED: CalendarClock,
46
+ ASSIGNMENT_CHANGE: UserCheck,
47
+ };
48
+
49
+ const interactionColors: Record<string, string> = {
50
+ CALL: 'bg-blue-50 text-blue-500',
51
+ SMS: 'bg-emerald-50 text-emerald-500',
52
+ EMAIL: 'bg-orange-50 text-orange-500',
53
+ MEETING: 'bg-purple-50 text-purple-500',
54
+ NOTE: 'bg-gray-50 text-gray-500',
55
+ STATUS_CHANGE: 'bg-amber-50 text-amber-500',
56
+ CONTACT_UPDATE: 'bg-cyan-50 text-cyan-500',
57
+ APPOINTMENT_CREATED: 'bg-emerald-50 text-emerald-500',
58
+ APPOINTMENT_DELETED: 'bg-red-50 text-red-500',
59
+ APPOINTMENT_CHANGED: 'bg-orange-50 text-orange-500',
60
+ ASSIGNMENT_CHANGE: 'bg-pink-50 text-pink-500',
61
+ };
62
+
63
+ const interactionLabels: Record<string, string> = {
64
+ CALL: 'Appel',
65
+ SMS: 'SMS',
66
+ EMAIL: 'Email',
67
+ MEETING: 'Réunion',
68
+ NOTE: 'Note',
69
+ STATUS_CHANGE: 'Changement de statut',
70
+ CONTACT_UPDATE: 'Mise à jour',
71
+ APPOINTMENT_CREATED: 'RDV créé',
72
+ APPOINTMENT_DELETED: 'RDV supprimé',
73
+ APPOINTMENT_CHANGED: 'RDV modifié',
74
+ ASSIGNMENT_CHANGE: 'Assignation',
75
+ };
76
+
77
+ export function RecentActivity({ interactions }: RecentActivityProps) {
78
+ return (
79
+ <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
80
+ <div className="mb-4 flex items-center justify-between">
81
+ <div>
82
+ <h3 className="text-base font-semibold text-gray-900">Activité Récente</h3>
83
+ <p className="mt-0.5 text-xs text-gray-400">Dernières interactions</p>
84
+ </div>
85
+ <Link href="/contacts" className="dash-link cursor-pointer text-xs font-medium">
86
+ Voir tout →
87
+ </Link>
88
+ </div>
89
+
90
+ {interactions.length === 0 ? (
91
+ <div className="flex flex-1 items-center justify-center">
92
+ <div className="text-center">
93
+ <FileText className="mx-auto h-10 w-10 text-gray-200" />
94
+ <p className="mt-2 text-sm text-gray-400">Aucune activité récente</p>
95
+ </div>
96
+ </div>
97
+ ) : (
98
+ <div className="flex-1 space-y-1 overflow-auto">
99
+ {interactions.map((interaction) => {
100
+ const Icon = interactionIcons[interaction.type] || FileText;
101
+ const color = interactionColors[interaction.type] || 'bg-gray-50 text-gray-500';
102
+ const label = interactionLabels[interaction.type] || interaction.type;
103
+ const date = new Date(interaction.date);
104
+ const now = new Date();
105
+ const diffMs = now.getTime() - date.getTime();
106
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
107
+ const diffMinutes = Math.floor(diffMs / (1000 * 60));
108
+
109
+ let timeAgo = '';
110
+ if (diffMinutes < 1) {
111
+ timeAgo = "À l'instant";
112
+ } else if (diffMinutes < 60) {
113
+ timeAgo = `Il y a ${diffMinutes} min`;
114
+ } else if (diffHours < 24) {
115
+ timeAgo = `Il y a ${diffHours}h`;
116
+ } else {
117
+ timeAgo = date.toLocaleDateString('fr-FR', {
118
+ day: 'numeric',
119
+ month: 'short',
120
+ });
121
+ }
122
+
123
+ return (
124
+ <div
125
+ key={interaction.id}
126
+ className="group dash-hover-border-left flex items-start gap-3 rounded-xl border-l-2 border-transparent px-3 py-2.5 transition-colors duration-150"
127
+ >
128
+ <div className={cn('rounded-lg p-1.5', color)}>
129
+ <Icon className="h-3.5 w-3.5" />
130
+ </div>
131
+ <div className="min-w-0 flex-1">
132
+ <div className="flex items-start justify-between gap-2">
133
+ <div className="min-w-0">
134
+ <p className="text-sm font-medium text-gray-900">{label}</p>
135
+ <Link
136
+ href={`/contacts/${interaction.contact.id}`}
137
+ className="dash-hover-text cursor-pointer text-xs text-gray-500 transition-colors"
138
+ >
139
+ {interaction.contact.name}
140
+ </Link>
141
+ </div>
142
+ <span className="shrink-0 text-[10px] font-medium text-gray-400">
143
+ {timeAgo}
144
+ </span>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ );
149
+ })}
150
+ </div>
151
+ )}
152
+ </div>
153
+ );
154
+ }
@@ -1,61 +1,61 @@
1
1
  'use client';
2
2
 
3
- import { LucideIcon } from 'lucide-react';
4
3
  import { cn } from '@/lib/utils';
5
4
 
6
5
  interface StatCardProps {
7
- title: string;
8
- value: string | number;
9
- icon: LucideIcon;
10
- trend?: {
11
- value: number;
12
- label: string;
6
+ readonly title: string;
7
+ readonly value: string | number;
8
+ readonly trend?: {
9
+ readonly value: number;
10
+ readonly label: string;
13
11
  };
14
- iconColor?: string;
15
- iconBgColor?: string;
12
+ readonly subtitle?: string;
13
+ readonly accentColor?: string;
16
14
  }
17
15
 
18
16
  export function StatCard({
19
17
  title,
20
18
  value,
21
- icon: Icon,
22
19
  trend,
23
- iconColor = 'text-primary',
24
- iconBgColor = 'bg-primary/15',
20
+ subtitle,
21
+ accentColor = 'dash-accent-bar',
25
22
  }: StatCardProps) {
26
23
  return (
27
- <div className="group relative overflow-hidden rounded-xl border border-border bg-card p-6 shadow-(--shadow-card) transition-all duration-300 hover:-translate-y-1">
28
- <div className="absolute inset-0 bg-linear-to-br from-card via-card to-muted/40 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
29
- <div className="relative flex items-center justify-between">
24
+ <div className="group ui-lift-hover relative flex h-full flex-col justify-between overflow-hidden rounded-2xl border border-gray-100 bg-white p-5 shadow-sm transition-shadow duration-200 hover:shadow-md">
25
+ <div className={cn('absolute top-0 left-0 h-1 w-full', accentColor)} />
26
+
27
+ <div className="flex items-start justify-between">
30
28
  <div className="flex-1">
31
- <p className="text-sm font-medium text-muted-foreground">{title}</p>
32
- <p className="mt-2 bg-linear-to-r from-foreground to-foreground/80 bg-clip-text text-3xl font-bold text-transparent">
33
- {value}
34
- </p>
35
- {trend && (
36
- <p className="mt-2 flex items-center gap-1 text-sm">
37
- <span
38
- className={cn(
39
- 'font-semibold',
40
- trend.value >= 0 ? 'text-emerald-600' : 'text-red-600',
41
- )}
42
- >
43
- {trend.value >= 0 ? '+' : ''}
44
- {trend.value}%
45
- </span>
46
- <span className="text-muted-foreground">{trend.label}</span>
47
- </p>
48
- )}
29
+ <p className="text-sm font-medium text-gray-500">{title}</p>
30
+ <p className="mt-2 text-3xl font-bold tracking-tight text-gray-900">{value}</p>
49
31
  </div>
50
- <div
51
- className={cn(
52
- 'rounded-xl bg-linear-to-br p-4 shadow-sm transition-transform duration-300 group-hover:scale-110',
53
- iconBgColor,
54
- )}
55
- >
56
- <Icon className={cn('h-6 w-6', iconColor)} />
32
+
33
+ <div className="flex items-end gap-[3px] opacity-60">
34
+ {[40, 65, 45, 80, 55, 70, 90].map((barH) => (
35
+ <div
36
+ key={`bar-${barH}`}
37
+ className={cn('w-[4px] rounded-full', accentColor)}
38
+ style={{ height: `${barH * 0.3}px`, opacity: 0.4 + (barH / 90) * 0.6 }}
39
+ />
40
+ ))}
57
41
  </div>
58
42
  </div>
43
+
44
+ <div className="mt-3 flex items-center gap-2">
45
+ {trend && (
46
+ <span
47
+ className={cn(
48
+ 'inline-flex items-center gap-0.5 rounded-full px-2 py-0.5 text-xs font-semibold',
49
+ trend.value >= 0 ? 'bg-emerald-50 text-emerald-600' : 'bg-red-50 text-red-600',
50
+ )}
51
+ >
52
+ {trend.value >= 0 ? '↑' : '↓'} {Math.abs(trend.value)}%
53
+ </span>
54
+ )}
55
+ {(subtitle || trend?.label) && (
56
+ <span className="text-xs text-gray-400">{subtitle || trend?.label}</span>
57
+ )}
58
+ </div>
59
59
  </div>
60
60
  );
61
61
  }
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Bar,
5
+ BarChart,
6
+ ResponsiveContainer,
7
+ Tooltip,
8
+ XAxis,
9
+ YAxis,
10
+ CartesianGrid,
11
+ Cell,
12
+ } from 'recharts';
13
+ import { useDashboardTheme } from '@/contexts/dashboard-theme-context';
14
+
15
+ interface StatusDistributionChartProps {
16
+ readonly data: Array<{ name: string; value: number }>;
17
+ }
18
+
19
+ export function StatusDistributionChart({ data }: Readonly<StatusDistributionChartProps>) {
20
+ const { theme } = useDashboardTheme();
21
+
22
+ const statusColors = [
23
+ theme.hex[500],
24
+ theme.hex[400],
25
+ theme.hex[300],
26
+ theme.hex[200],
27
+ '#10b981',
28
+ '#34d399',
29
+ '#6ee7b7',
30
+ '#3b82f6',
31
+ '#60a5fa',
32
+ '#93c5fd',
33
+ ];
34
+
35
+ return (
36
+ <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
37
+ <div className="mb-4">
38
+ <h3 className="text-base font-semibold text-gray-900">Répartition par Statut</h3>
39
+ <p className="mt-0.5 text-xs text-gray-400">Distribution des contacts</p>
40
+ </div>
41
+ <div className="min-h-0 flex-1">
42
+ <ResponsiveContainer width="100%" height="100%">
43
+ <BarChart
44
+ data={data}
45
+ layout="vertical"
46
+ margin={{ top: 0, right: 20, left: 0, bottom: 0 }}
47
+ >
48
+ <CartesianGrid strokeDasharray="3 3" stroke="#f3f4f6" horizontal={false} />
49
+ <XAxis type="number" stroke="#d1d5db" fontSize={11} tickLine={false} axisLine={false} />
50
+ <YAxis
51
+ dataKey="name"
52
+ type="category"
53
+ stroke="#d1d5db"
54
+ fontSize={11}
55
+ tickLine={false}
56
+ axisLine={false}
57
+ width={90}
58
+ />
59
+ <Tooltip
60
+ contentStyle={{
61
+ backgroundColor: '#fff',
62
+ border: '1px solid #f3f4f6',
63
+ borderRadius: '12px',
64
+ boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
65
+ fontSize: '13px',
66
+ }}
67
+ />
68
+ <Bar dataKey="value" radius={[0, 6, 6, 0]} name="Contacts" barSize={20}>
69
+ {data.map((entry, index) => (
70
+ <Cell
71
+ key={`status-${entry.name}`}
72
+ fill={statusColors[index % statusColors.length]}
73
+ />
74
+ ))}
75
+ </Bar>
76
+ </BarChart>
77
+ </ResponsiveContainer>
78
+ </div>
79
+ </div>
80
+ );
81
+ }
@@ -1,18 +1,21 @@
1
1
  'use client';
2
2
 
3
3
  import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
4
+ import { useDashboardTheme } from '@/contexts/dashboard-theme-context';
4
5
 
5
6
  interface TasksPieChartProps {
6
- completed: number;
7
- pending: number;
7
+ readonly completed: number;
8
+ readonly pending: number;
8
9
  }
9
10
 
10
- const COLORS = {
11
- completed: '#10b981',
12
- pending: '#f59e0b',
13
- };
11
+ export function TasksPieChart({ completed, pending }: Readonly<TasksPieChartProps>) {
12
+ const { theme } = useDashboardTheme();
13
+
14
+ const COLORS = {
15
+ completed: '#10b981',
16
+ pending: theme.hex[500],
17
+ };
14
18
 
15
- export function TasksPieChart({ completed, pending }: TasksPieChartProps) {
16
19
  const data = [
17
20
  { name: 'Complétées', value: completed },
18
21
  { name: 'En attente', value: pending },
@@ -22,64 +25,64 @@ export function TasksPieChart({ completed, pending }: TasksPieChartProps) {
22
25
  const completionRate = total > 0 ? Math.round((completed / total) * 100) : 0;
23
26
 
24
27
  return (
25
- <div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
26
- <div className="mb-4">
27
- <h3 className="text-lg font-bold text-gray-900">Statut des Tâches</h3>
28
- <p className="mt-1 text-sm text-gray-500">Répartition des tâches</p>
28
+ <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
29
+ <div className="mb-2">
30
+ <h3 className="text-base font-semibold text-gray-900">Statut des Tâches</h3>
31
+ <p className="mt-0.5 text-xs text-gray-400">Répartition des tâches</p>
29
32
  </div>
30
- <div className="flex items-center justify-center">
31
- <div className="relative h-[200px] w-[200px]">
33
+ <div className="flex min-h-0 flex-1 items-center justify-center">
34
+ <div className="relative h-[160px] w-[160px]">
32
35
  <ResponsiveContainer width="100%" height="100%">
33
36
  <PieChart>
34
37
  <Pie
35
38
  data={data}
36
39
  cx="50%"
37
40
  cy="50%"
38
- innerRadius={60}
39
- outerRadius={80}
40
- paddingAngle={2}
41
+ innerRadius={50}
42
+ outerRadius={70}
43
+ paddingAngle={3}
41
44
  dataKey="value"
45
+ strokeWidth={0}
42
46
  >
43
- {data.map((entry, index) => (
47
+ {data.map((entry) => (
44
48
  <Cell
45
- key={`cell-${index}`}
49
+ key={entry.name}
46
50
  fill={entry.name === 'Complétées' ? COLORS.completed : COLORS.pending}
47
51
  />
48
52
  ))}
49
53
  </Pie>
50
54
  <Tooltip
51
55
  contentStyle={{
52
- backgroundColor: 'white',
53
- border: '1px solid #e5e7eb',
56
+ backgroundColor: '#fff',
57
+ border: '1px solid #f3f4f6',
54
58
  borderRadius: '12px',
55
- boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
59
+ boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
60
+ fontSize: '13px',
56
61
  }}
57
62
  />
58
63
  </PieChart>
59
64
  </ResponsiveContainer>
60
65
  <div className="absolute inset-0 flex items-center justify-center">
61
66
  <div className="text-center">
62
- <p className="bg-linear-to-r from-gray-900 to-gray-700 bg-clip-text text-3xl font-bold text-transparent">
63
- {completionRate}%
64
- </p>
65
- <p className="text-xs text-gray-500">Complétées</p>
67
+ <p className="text-2xl font-bold text-gray-900">{completionRate}%</p>
68
+ <p className="text-[10px] text-gray-400">Complétées</p>
66
69
  </div>
67
70
  </div>
68
71
  </div>
69
72
  </div>
70
- <div className="mt-4 grid grid-cols-2 gap-4">
71
- <div className="flex items-center gap-2 rounded-lg bg-emerald-50 p-2">
72
- <div className="h-3 w-3 rounded-full bg-emerald-500" />
73
+ <div className="mt-2 grid grid-cols-2 gap-2">
74
+ <div className="flex items-center gap-2 rounded-xl bg-emerald-50/80 px-3 py-2">
75
+ <div className="h-2.5 w-2.5 rounded-full bg-emerald-500" />
73
76
  <div>
74
- <p className="text-xs text-gray-500">Complétées</p>
75
- <p className="font-semibold text-gray-900">{completed}</p>
77
+ <p className="text-[10px] text-gray-500">Complétées</p>
78
+ <p className="text-sm font-semibold text-gray-900">{completed}</p>
76
79
  </div>
77
80
  </div>
78
- <div className="flex items-center gap-2 rounded-lg bg-blue-50 p-2">
79
- <div className="h-3 w-3 rounded-full bg-blue-500" />
81
+ <div className="dash-legend-bg flex items-center gap-2 rounded-xl px-3 py-2">
82
+ <div className="dash-legend-dot h-2.5 w-2.5 rounded-full" />
80
83
  <div>
81
- <p className="text-xs text-gray-500">En attente</p>
82
- <p className="font-semibold text-gray-900">{pending}</p>
84
+ <p className="text-[10px] text-gray-500">En attente</p>
85
+ <p className="text-sm font-semibold text-gray-900">{pending}</p>
83
86
  </div>
84
87
  </div>
85
88
  </div>