create-crm-tmp 1.1.2 → 2.0.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 (220) hide show
  1. package/package.json +1 -1
  2. package/template/.prettierignore +2 -0
  3. package/template/README.md +53 -67
  4. package/template/components.json +22 -0
  5. package/template/exemple-contacts.csv +54 -0
  6. package/template/next.config.ts +27 -1
  7. package/template/package.json +64 -27
  8. package/template/prisma/schema.prisma +821 -72
  9. package/template/skills-lock.json +25 -0
  10. package/template/src/app/(auth)/invite/[token]/page.tsx +21 -24
  11. package/template/src/app/(auth)/reset-password/complete/page.tsx +12 -21
  12. package/template/src/app/(auth)/reset-password/page.tsx +12 -8
  13. package/template/src/app/(auth)/reset-password/verify/page.tsx +12 -8
  14. package/template/src/app/(auth)/signin/page.tsx +20 -17
  15. package/template/src/app/(dashboard)/agenda/page.tsx +2231 -2188
  16. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
  17. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +680 -323
  18. package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
  19. package/template/src/app/(dashboard)/automatisation/page.tsx +473 -180
  20. package/template/src/app/(dashboard)/closing/page.tsx +500 -468
  21. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +5035 -4126
  22. package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1703 -0
  23. package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
  24. package/template/src/app/(dashboard)/contacts/page.tsx +3776 -2064
  25. package/template/src/app/(dashboard)/dashboard/page.tsx +37 -519
  26. package/template/src/app/(dashboard)/error.tsx +37 -0
  27. package/template/src/app/(dashboard)/layout.tsx +1 -1
  28. package/template/src/app/(dashboard)/loading.tsx +5 -0
  29. package/template/src/app/(dashboard)/settings/loading.tsx +19 -0
  30. package/template/src/app/(dashboard)/settings/page.tsx +2685 -2489
  31. package/template/src/app/(dashboard)/templates/page.tsx +500 -300
  32. package/template/src/app/(dashboard)/users/list/page.tsx +356 -350
  33. package/template/src/app/(dashboard)/users/page.tsx +279 -310
  34. package/template/src/app/(dashboard)/users/permissions/page.tsx +104 -99
  35. package/template/src/app/(dashboard)/users/roles/page.tsx +164 -137
  36. package/template/src/app/api/audit-logs/route.ts +1 -1
  37. package/template/src/app/api/auth/google/callback/route.ts +8 -5
  38. package/template/src/app/api/auth/google/disconnect/route.ts +2 -2
  39. package/template/src/app/api/companies/[id]/activities/route.ts +131 -0
  40. package/template/src/app/api/companies/[id]/route.ts +195 -0
  41. package/template/src/app/api/companies/export/route.ts +206 -0
  42. package/template/src/app/api/companies/route.ts +166 -0
  43. package/template/src/app/api/contact-views/[id]/pin/route.ts +69 -0
  44. package/template/src/app/api/contact-views/[id]/route.ts +197 -0
  45. package/template/src/app/api/contact-views/route.ts +146 -0
  46. package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +77 -0
  47. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +7 -17
  48. package/template/src/app/api/contacts/[id]/files/route.ts +83 -44
  49. package/template/src/app/api/contacts/[id]/interactions/route.ts +37 -0
  50. package/template/src/app/api/contacts/[id]/kyc/route.ts +71 -0
  51. package/template/src/app/api/contacts/[id]/meet/route.ts +38 -29
  52. package/template/src/app/api/contacts/[id]/route.ts +111 -20
  53. package/template/src/app/api/contacts/[id]/send-email/route.ts +6 -0
  54. package/template/src/app/api/contacts/[id]/workflows/run/route.ts +61 -0
  55. package/template/src/app/api/contacts/export/route.ts +12 -17
  56. package/template/src/app/api/contacts/import/route.ts +22 -19
  57. package/template/src/app/api/contacts/import-preview/route.ts +139 -0
  58. package/template/src/app/api/contacts/route.ts +202 -49
  59. package/template/src/app/api/dashboard/stats/route.ts +9 -292
  60. package/template/src/app/api/integrations/google-sheet/sync/route.ts +203 -185
  61. package/template/src/app/api/invite/complete/route.ts +20 -23
  62. package/template/src/app/api/reminders/route.ts +1 -0
  63. package/template/src/app/api/reset-password/complete/route.ts +11 -13
  64. package/template/src/app/api/send/route.ts +9 -85
  65. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +10 -21
  66. package/template/src/app/api/settings/closing-reasons/route.ts +10 -21
  67. package/template/src/app/api/settings/company/route.ts +19 -26
  68. package/template/src/app/api/settings/google-ads/[id]/route.ts +20 -23
  69. package/template/src/app/api/settings/google-ads/route.ts +20 -23
  70. package/template/src/app/api/settings/google-sheet/[id]/route.ts +20 -23
  71. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +23 -32
  72. package/template/src/app/api/settings/google-sheet/preview/route.ts +104 -0
  73. package/template/src/app/api/settings/google-sheet/route.ts +20 -23
  74. package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -23
  75. package/template/src/app/api/settings/meta-leads/route.ts +20 -23
  76. package/template/src/app/api/settings/statuses/[id]/route.ts +33 -23
  77. package/template/src/app/api/settings/statuses/route.ts +24 -22
  78. package/template/src/app/api/statuses/route.ts +2 -5
  79. package/template/src/app/api/tasks/[id]/attendees/route.ts +14 -7
  80. package/template/src/app/api/tasks/[id]/route.ts +161 -137
  81. package/template/src/app/api/tasks/meet/route.ts +11 -8
  82. package/template/src/app/api/tasks/route.ts +155 -95
  83. package/template/src/app/api/templates/[id]/route.ts +22 -13
  84. package/template/src/app/api/templates/route.ts +22 -5
  85. package/template/src/app/api/users/[id]/resend-invite/route.ts +95 -0
  86. package/template/src/app/api/users/[id]/route.ts +16 -1
  87. package/template/src/app/api/users/commercials/route.ts +38 -0
  88. package/template/src/app/api/users/for-agenda/route.ts +1 -2
  89. package/template/src/app/api/users/route.ts +94 -55
  90. package/template/src/app/api/webhooks/google-ads/route.ts +20 -1
  91. package/template/src/app/api/webhooks/meta-leads/route.ts +18 -1
  92. package/template/src/app/api/workflows/[id]/route.ts +33 -6
  93. package/template/src/app/api/workflows/process/route.ts +509 -146
  94. package/template/src/app/api/workflows/route.ts +46 -4
  95. package/template/src/app/globals.css +210 -101
  96. package/template/src/app/layout.tsx +19 -8
  97. package/template/src/app/page.tsx +37 -7
  98. package/template/src/components/address-autocomplete.tsx +232 -0
  99. package/template/src/components/contacts/filter-bar.tsx +181 -0
  100. package/template/src/components/contacts/filter-builder.tsx +589 -0
  101. package/template/src/components/contacts/save-view-dialog.tsx +160 -0
  102. package/template/src/components/contacts/views-tab-bar.tsx +440 -0
  103. package/template/src/components/dashboard/activity-chart.tsx +31 -39
  104. package/template/src/components/dashboard/dashboard-content.tsx +79 -0
  105. package/template/src/components/dashboard/stat-card.tsx +40 -42
  106. package/template/src/components/dashboard/tasks-pie-chart.tsx +34 -37
  107. package/template/src/components/dashboard/upcoming-tasks-list.tsx +78 -72
  108. package/template/src/components/date-picker.tsx +396 -0
  109. package/template/src/components/editor.tsx +27 -13
  110. package/template/src/components/email-template.tsx +4 -2
  111. package/template/src/components/global-search.tsx +358 -0
  112. package/template/src/components/header.tsx +57 -62
  113. package/template/src/components/invitation-email-template.tsx +4 -2
  114. package/template/src/components/lazy-editor.tsx +11 -0
  115. package/template/src/components/meet-cancellation-email-template.tsx +11 -3
  116. package/template/src/components/meet-confirmation-email-template.tsx +10 -3
  117. package/template/src/components/meet-update-email-template.tsx +10 -3
  118. package/template/src/components/page-header.tsx +19 -15
  119. package/template/src/components/protected-page.tsx +94 -0
  120. package/template/src/components/reset-password-email-template.tsx +4 -2
  121. package/template/src/components/sidebar.tsx +92 -94
  122. package/template/src/components/skeleton.tsx +128 -42
  123. package/template/src/components/ui/accordion.tsx +64 -0
  124. package/template/src/components/ui/alert-dialog.tsx +139 -0
  125. package/template/src/components/ui/button.tsx +60 -0
  126. package/template/src/components/view-as-banner.tsx +1 -1
  127. package/template/src/components/view-as-modal.tsx +21 -16
  128. package/template/src/config/nav-pages.ts +108 -0
  129. package/template/src/contexts/app-toast-context.tsx +174 -0
  130. package/template/src/contexts/sidebar-context.tsx +16 -47
  131. package/template/src/contexts/task-reminder-context.tsx +6 -6
  132. package/template/src/contexts/view-as-context.tsx +11 -16
  133. package/template/src/hooks/use-alert.tsx +65 -0
  134. package/template/src/hooks/use-confirm.tsx +87 -0
  135. package/template/src/hooks/use-contact-views.ts +140 -0
  136. package/template/src/hooks/use-contacts.ts +69 -0
  137. package/template/src/hooks/use-fetch.ts +17 -0
  138. package/template/src/hooks/use-focus-trap.ts +73 -0
  139. package/template/src/hooks/use-statuses.ts +22 -0
  140. package/template/src/lib/address-api.ts +155 -0
  141. package/template/src/lib/cache.ts +73 -0
  142. package/template/src/lib/check-permission.ts +12 -177
  143. package/template/src/lib/contact-interactions.ts +3 -1
  144. package/template/src/lib/contact-view-filters.ts +341 -0
  145. package/template/src/lib/dashboard-stats.ts +224 -0
  146. package/template/src/lib/date-utils.ts +49 -0
  147. package/template/src/lib/get-auth-user.ts +25 -0
  148. package/template/src/lib/google-calendar.ts +54 -12
  149. package/template/src/lib/google-drive.ts +796 -75
  150. package/template/src/lib/google-fetch.ts +63 -0
  151. package/template/src/lib/local-storage.ts +34 -0
  152. package/template/src/lib/permissions.ts +245 -47
  153. package/template/src/lib/prisma.ts +11 -11
  154. package/template/src/lib/roles.ts +14 -39
  155. package/template/src/lib/template-variables.ts +67 -33
  156. package/template/src/lib/utils.ts +26 -2
  157. package/template/src/lib/workflow-executor.ts +445 -229
  158. package/template/src/proxy.ts +34 -73
  159. package/template/src/types/contact-views.ts +351 -0
  160. package/template/src/types/yousign.ts +52 -0
  161. package/template/vercel.json +12 -0
  162. package/template/WORKFLOWS_CRON.md +0 -185
  163. package/template/prisma/migrations/20251126144728_init/migration.sql +0 -78
  164. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +0 -5
  165. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +0 -19
  166. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +0 -22
  167. package/template/prisma/migrations/20251128132303_add_status/migration.sql +0 -23
  168. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +0 -75
  169. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +0 -2
  170. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +0 -45
  171. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +0 -2
  172. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +0 -27
  173. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +0 -20
  174. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +0 -18
  175. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +0 -32
  176. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +0 -20
  177. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +0 -12
  178. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +0 -21
  179. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +0 -11
  180. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +0 -12
  181. package/template/prisma/migrations/20251208094843_mg/migration.sql +0 -14
  182. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +0 -14
  183. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +0 -26
  184. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +0 -2
  185. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +0 -2
  186. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +0 -2
  187. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +0 -3
  188. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +0 -21
  189. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +0 -2
  190. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +0 -10
  191. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +0 -26
  192. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +0 -24
  193. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +0 -11
  194. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +0 -12
  195. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +0 -25
  196. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +0 -8
  197. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +0 -2
  198. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +0 -80
  199. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +0 -32
  200. package/template/prisma/migrations/20251220000000_add_task_interaction_type/migration.sql +0 -4
  201. package/template/prisma/migrations/20251221000000_add_task_type/migration.sql +0 -3
  202. package/template/prisma/migrations/20251221000001_add_event_color/migration.sql +0 -23
  203. package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +0 -20
  204. package/template/prisma/migrations/migration_lock.toml +0 -3
  205. package/template/src/app/(dashboard)/users/layout.tsx +0 -30
  206. package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -47
  207. package/template/src/app/api/dashboard/widgets/route.ts +0 -181
  208. package/template/src/components/dashboard/add-widget-dialog.tsx +0 -161
  209. package/template/src/components/dashboard/color-picker.tsx +0 -65
  210. package/template/src/components/dashboard/contacts-chart.tsx +0 -69
  211. package/template/src/components/dashboard/interactions-by-type-chart.tsx +0 -121
  212. package/template/src/components/dashboard/recent-activity.tsx +0 -157
  213. package/template/src/components/dashboard/sales-analytics-chart.tsx +0 -77
  214. package/template/src/components/dashboard/status-distribution-chart.tsx +0 -82
  215. package/template/src/components/dashboard/top-contacts-list.tsx +0 -119
  216. package/template/src/components/dashboard/widget-wrapper.tsx +0 -39
  217. package/template/src/contexts/dashboard-theme-context.tsx +0 -58
  218. package/template/src/lib/dashboard-themes.ts +0 -140
  219. package/template/src/lib/default-widgets.ts +0 -14
  220. package/template/src/lib/widget-registry.ts +0 -177
@@ -1,157 +0,0 @@
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
86
- href="/contacts"
87
- className="dash-link cursor-pointer text-xs font-medium"
88
- >
89
- Voir tout →
90
- </Link>
91
- </div>
92
-
93
- {interactions.length === 0 ? (
94
- <div className="flex flex-1 items-center justify-center">
95
- <div className="text-center">
96
- <FileText className="mx-auto h-10 w-10 text-gray-200" />
97
- <p className="mt-2 text-sm text-gray-400">Aucune activité récente</p>
98
- </div>
99
- </div>
100
- ) : (
101
- <div className="flex-1 space-y-1 overflow-auto">
102
- {interactions.map((interaction) => {
103
- const Icon = interactionIcons[interaction.type] || FileText;
104
- const color = interactionColors[interaction.type] || 'bg-gray-50 text-gray-500';
105
- const label = interactionLabels[interaction.type] || interaction.type;
106
- const date = new Date(interaction.date);
107
- const now = new Date();
108
- const diffMs = now.getTime() - date.getTime();
109
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
110
- const diffMinutes = Math.floor(diffMs / (1000 * 60));
111
-
112
- let timeAgo = '';
113
- if (diffMinutes < 1) {
114
- timeAgo = "À l'instant";
115
- } else if (diffMinutes < 60) {
116
- timeAgo = `Il y a ${diffMinutes} min`;
117
- } else if (diffHours < 24) {
118
- timeAgo = `Il y a ${diffHours}h`;
119
- } else {
120
- timeAgo = date.toLocaleDateString('fr-FR', {
121
- day: 'numeric',
122
- month: 'short',
123
- });
124
- }
125
-
126
- return (
127
- <div
128
- key={interaction.id}
129
- className="group flex items-start gap-3 rounded-xl border-l-2 border-transparent px-3 py-2.5 transition-all duration-150 dash-hover-border-left"
130
- >
131
- <div className={cn('rounded-lg p-1.5', color)}>
132
- <Icon className="h-3.5 w-3.5" />
133
- </div>
134
- <div className="min-w-0 flex-1">
135
- <div className="flex items-start justify-between gap-2">
136
- <div className="min-w-0">
137
- <p className="text-sm font-medium text-gray-900">{label}</p>
138
- <Link
139
- href={`/contacts/${interaction.contact.id}`}
140
- className="dash-hover-text cursor-pointer text-xs text-gray-500 transition-colors"
141
- >
142
- {interaction.contact.name}
143
- </Link>
144
- </div>
145
- <span className="shrink-0 text-[10px] font-medium text-gray-400">
146
- {timeAgo}
147
- </span>
148
- </div>
149
- </div>
150
- </div>
151
- );
152
- })}
153
- </div>
154
- )}
155
- </div>
156
- );
157
- }
@@ -1,77 +0,0 @@
1
- 'use client';
2
-
3
- import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
4
-
5
- interface SalesAnalyticsChartProps {
6
- data: Array<{ month: string; count: number }>;
7
- }
8
-
9
- export function SalesAnalyticsChart({ data }: SalesAnalyticsChartProps) {
10
- // Calculer le total et la croissance
11
- const total = data.reduce((sum, item) => sum + item.count, 0);
12
- const previousTotal =
13
- data.length > 1 ? data.slice(0, -1).reduce((sum, item) => sum + item.count, 0) : 0;
14
- const growth = previousTotal > 0 ? ((total - previousTotal) / previousTotal) * 100 : 0;
15
-
16
- return (
17
- <div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
18
- <div className="mb-4 flex items-center justify-between">
19
- <div>
20
- <h3 className="text-lg font-bold text-gray-900">Analytiques des Ventes</h3>
21
- </div>
22
- <select className="rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none">
23
- <option>Mensuel</option>
24
- <option>Hebdomadaire</option>
25
- <option>Annuel</option>
26
- </select>
27
- </div>
28
- <div className="mb-4 flex items-baseline gap-4">
29
- <div>
30
- <p className="text-2xl font-bold text-gray-900">
31
- {total.toLocaleString('fr-FR')} contacts ce mois
32
- </p>
33
- <p className="mt-1 text-sm font-semibold text-emerald-600">
34
- Augmentation ce mois : +{growth.toFixed(1)}%
35
- </p>
36
- </div>
37
- </div>
38
- <div className="h-[280px]">
39
- <ResponsiveContainer width="100%" height="100%">
40
- <BarChart data={data} barCategoryGap="20%">
41
- <defs>
42
- <linearGradient id="barGradient" x1="0" y1="0" x2="0" y2="1">
43
- <stop offset="0%" stopColor="#8b5cf6" stopOpacity={1} />
44
- <stop offset="100%" stopColor="#6366f1" stopOpacity={0.8} />
45
- </linearGradient>
46
- </defs>
47
- <XAxis
48
- dataKey="month"
49
- stroke="#9ca3af"
50
- fontSize={12}
51
- tickLine={false}
52
- axisLine={false}
53
- />
54
- <YAxis
55
- stroke="#9ca3af"
56
- fontSize={12}
57
- tickLine={false}
58
- axisLine={false}
59
- tickFormatter={(value) => `${value}`}
60
- />
61
- <Tooltip
62
- contentStyle={{
63
- backgroundColor: 'white',
64
- border: '1px solid #e5e7eb',
65
- borderRadius: '12px',
66
- boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
67
- }}
68
- labelStyle={{ color: '#374151', fontWeight: 600 }}
69
- formatter={(value: number) => [`${value.toLocaleString('fr-FR')}`, 'Contacts']}
70
- />
71
- <Bar dataKey="count" fill="url(#barGradient)" radius={[8, 8, 0, 0]} />
72
- </BarChart>
73
- </ResponsiveContainer>
74
- </div>
75
- </div>
76
- );
77
- }
@@ -1,82 +0,0 @@
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
- // Générer une palette basée sur le thème actif + couleurs complémentaires
23
- const statusColors = [
24
- theme.hex[500],
25
- theme.hex[400],
26
- theme.hex[300],
27
- theme.hex[200],
28
- '#10b981',
29
- '#34d399',
30
- '#6ee7b7',
31
- '#3b82f6',
32
- '#60a5fa',
33
- '#93c5fd',
34
- ];
35
-
36
- return (
37
- <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
38
- <div className="mb-4">
39
- <h3 className="text-base font-semibold text-gray-900">Répartition par Statut</h3>
40
- <p className="mt-0.5 text-xs text-gray-400">Distribution des contacts</p>
41
- </div>
42
- <div className="min-h-0 flex-1">
43
- <ResponsiveContainer width="100%" height="100%">
44
- <BarChart
45
- data={data}
46
- layout="vertical"
47
- margin={{ top: 0, right: 20, left: 0, bottom: 0 }}
48
- >
49
- <CartesianGrid strokeDasharray="3 3" stroke="#f3f4f6" horizontal={false} />
50
- <XAxis type="number" stroke="#d1d5db" fontSize={11} tickLine={false} axisLine={false} />
51
- <YAxis
52
- dataKey="name"
53
- type="category"
54
- stroke="#d1d5db"
55
- fontSize={11}
56
- tickLine={false}
57
- axisLine={false}
58
- width={90}
59
- />
60
- <Tooltip
61
- contentStyle={{
62
- backgroundColor: '#fff',
63
- border: '1px solid #f3f4f6',
64
- borderRadius: '12px',
65
- boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
66
- fontSize: '13px',
67
- }}
68
- />
69
- <Bar dataKey="value" radius={[0, 6, 6, 0]} name="Contacts" barSize={20}>
70
- {data.map((entry, index) => (
71
- <Cell
72
- key={`status-${entry.name}`}
73
- fill={statusColors[index % statusColors.length]}
74
- />
75
- ))}
76
- </Bar>
77
- </BarChart>
78
- </ResponsiveContainer>
79
- </div>
80
- </div>
81
- );
82
- }
@@ -1,119 +0,0 @@
1
- 'use client';
2
-
3
- import Link from 'next/link';
4
- import { Users } from 'lucide-react';
5
-
6
- interface Contact {
7
- id: string;
8
- name: string;
9
- phone: string;
10
- email: string | null;
11
- status: string;
12
- interactionsCount: number;
13
- assignedCommercial?: string;
14
- assignedTelepro?: string;
15
- }
16
-
17
- interface TopContactsListProps {
18
- readonly contacts: Contact[];
19
- }
20
-
21
- export function TopContactsList({ contacts }: TopContactsListProps) {
22
- if (contacts.length === 0) {
23
- return (
24
- <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
25
- <div className="flex items-center justify-between">
26
- <div>
27
- <h3 className="text-base font-semibold text-gray-900">Derniers Prospects</h3>
28
- <p className="mt-0.5 text-xs text-gray-400">Contacts récemment ajoutés</p>
29
- </div>
30
- <Link
31
- href="/contacts"
32
- className="dash-link cursor-pointer text-xs font-medium"
33
- >
34
- Voir tout →
35
- </Link>
36
- </div>
37
- <div className="flex flex-1 items-center justify-center">
38
- <div className="text-center">
39
- <Users className="mx-auto h-10 w-10 text-gray-200" />
40
- <p className="mt-2 text-sm text-gray-400">Aucun contact</p>
41
- </div>
42
- </div>
43
- </div>
44
- );
45
- }
46
-
47
- return (
48
- <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
49
- <div className="mb-4 flex items-center justify-between">
50
- <div>
51
- <h3 className="text-base font-semibold text-gray-900">Derniers Prospects</h3>
52
- <p className="mt-0.5 text-xs text-gray-400">Contacts récemment ajoutés</p>
53
- </div>
54
- <Link
55
- href="/contacts"
56
- className="dash-link cursor-pointer text-xs font-medium"
57
- >
58
- Voir tout →
59
- </Link>
60
- </div>
61
- <div className="flex-1 overflow-auto">
62
- <table className="w-full">
63
- <thead>
64
- <tr className="border-b border-gray-100">
65
- <th className="pb-2.5 text-left text-[11px] font-medium tracking-wider text-gray-400 uppercase">
66
- Prospect
67
- </th>
68
- <th className="pb-2.5 text-left text-[11px] font-medium tracking-wider text-gray-400 uppercase">
69
- Email
70
- </th>
71
- <th className="pb-2.5 text-left text-[11px] font-medium tracking-wider text-gray-400 uppercase">
72
- Télépro
73
- </th>
74
- <th className="pb-2.5 text-left text-[11px] font-medium tracking-wider text-gray-400 uppercase">
75
- Statut
76
- </th>
77
- </tr>
78
- </thead>
79
- <tbody>
80
- {contacts.map((contact) => {
81
- const nameParts = contact.name.split(' ');
82
- const firstName = nameParts[0] || '';
83
- const lastName = nameParts.slice(1).join(' ') || '';
84
- const initials = `${firstName[0] || ''}${lastName[0] || ''}`.toUpperCase();
85
-
86
- return (
87
- <tr
88
- key={contact.id}
89
- className="border-b border-gray-50 transition-colors dash-hover-bg"
90
- >
91
- <td className="py-3">
92
- <div className="flex items-center gap-2.5">
93
- <div className="dash-avatar flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold">
94
- {initials || '?'}
95
- </div>
96
- <Link
97
- href={`/contacts/${contact.id}`}
98
- className="dash-hover-text cursor-pointer text-sm font-medium text-gray-900 transition-colors"
99
- >
100
- {contact.name}
101
- </Link>
102
- </div>
103
- </td>
104
- <td className="py-3 text-xs text-gray-500">{contact.email || '-'}</td>
105
- <td className="py-3 text-xs text-gray-500">{contact.assignedTelepro || '-'}</td>
106
- <td className="py-3">
107
- <span className="inline-flex rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-medium text-gray-700">
108
- {contact.status}
109
- </span>
110
- </td>
111
- </tr>
112
- );
113
- })}
114
- </tbody>
115
- </table>
116
- </div>
117
- </div>
118
- );
119
- }
@@ -1,39 +0,0 @@
1
- 'use client';
2
-
3
- import { X, GripVertical } from 'lucide-react';
4
- import { cn } from '@/lib/utils';
5
-
6
- interface WidgetWrapperProps {
7
- readonly children: React.ReactNode;
8
- readonly onRemove?: () => void;
9
- readonly className?: string;
10
- }
11
-
12
- export function WidgetWrapper({ children, onRemove, className }: WidgetWrapperProps) {
13
- return (
14
- <div className={cn('group/widget relative h-full w-full', className)}>
15
- {/* Barre d'outils au hover (visible uniquement si des actions sont disponibles) */}
16
- {onRemove && (
17
- <div className="absolute top-0 right-0 z-10 flex items-center gap-1 opacity-0 transition-opacity duration-150 group-hover/widget:opacity-100">
18
- {/* Poignée de drag */}
19
- <div className="drag-handle flex h-7 w-7 cursor-grab items-center justify-center rounded-lg bg-white/90 shadow-sm ring-1 ring-gray-200/60 backdrop-blur-sm transition-colors hover:bg-gray-50 active:cursor-grabbing">
20
- <GripVertical className="h-3.5 w-3.5 text-gray-400" />
21
- </div>
22
- {/* Bouton supprimer */}
23
- <button
24
- onClick={(e) => {
25
- e.stopPropagation();
26
- onRemove();
27
- }}
28
- className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-lg bg-white/90 shadow-sm ring-1 ring-gray-200/60 backdrop-blur-sm transition-colors hover:bg-red-50 hover:text-red-600"
29
- >
30
- <X className="h-3.5 w-3.5" />
31
- </button>
32
- </div>
33
- )}
34
-
35
- {/* Contenu du widget */}
36
- {children}
37
- </div>
38
- );
39
- }
@@ -1,58 +0,0 @@
1
- 'use client';
2
-
3
- import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
4
- import {
5
- type DashboardTheme,
6
- DASHBOARD_THEMES,
7
- DEFAULT_THEME_KEY,
8
- getThemeByKey,
9
- } from '@/lib/dashboard-themes';
10
-
11
- interface DashboardThemeContextType {
12
- theme: DashboardTheme;
13
- setThemeKey: (key: string) => void;
14
- themes: DashboardTheme[];
15
- }
16
-
17
- const DashboardThemeContext = createContext<DashboardThemeContextType | undefined>(undefined);
18
-
19
- const STORAGE_KEY = 'dashboard_theme';
20
-
21
- export function DashboardThemeProvider({ children }: Readonly<{ children: ReactNode }>) {
22
- const [themeKey, setThemeKeyState] = useState(DEFAULT_THEME_KEY);
23
-
24
- // Charger le thème depuis le localStorage
25
- useEffect(() => {
26
- const stored = localStorage.getItem(STORAGE_KEY);
27
- if (stored && DASHBOARD_THEMES.some((t) => t.key === stored)) {
28
- setThemeKeyState(stored);
29
- }
30
- }, []);
31
-
32
- const setThemeKey = (key: string) => {
33
- setThemeKeyState(key);
34
- localStorage.setItem(STORAGE_KEY, key);
35
- };
36
-
37
- const theme = getThemeByKey(themeKey);
38
-
39
- const value = useMemo(
40
- () => ({ theme, setThemeKey, themes: DASHBOARD_THEMES }),
41
- // eslint-disable-next-line react-hooks/exhaustive-deps
42
- [theme],
43
- );
44
-
45
- return (
46
- <DashboardThemeContext.Provider value={value}>
47
- {children}
48
- </DashboardThemeContext.Provider>
49
- );
50
- }
51
-
52
- export function useDashboardTheme() {
53
- const context = useContext(DashboardThemeContext);
54
- if (!context) {
55
- throw new Error('useDashboardTheme doit être utilisé dans un DashboardThemeProvider');
56
- }
57
- return context;
58
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * Thèmes de couleur pour le tableau de bord
3
- * Chaque thème fournit des valeurs hex pour toutes les nuances nécessaires
4
- */
5
-
6
- export interface DashboardTheme {
7
- key: string;
8
- label: string;
9
- hex: {
10
- 50: string;
11
- 100: string;
12
- 200: string;
13
- 300: string;
14
- 400: string;
15
- 500: string;
16
- 600: string;
17
- 700: string;
18
- };
19
- }
20
-
21
- export const DASHBOARD_THEMES: DashboardTheme[] = [
22
- {
23
- key: 'orange',
24
- label: 'Orange',
25
- hex: {
26
- 50: '#fff7ed',
27
- 100: '#ffedd5',
28
- 200: '#fed7aa',
29
- 300: '#fdba74',
30
- 400: '#fb923c',
31
- 500: '#f97316',
32
- 600: '#ea580c',
33
- 700: '#c2410c',
34
- },
35
- },
36
- {
37
- key: 'blue',
38
- label: 'Bleu',
39
- hex: {
40
- 50: '#eff6ff',
41
- 100: '#dbeafe',
42
- 200: '#bfdbfe',
43
- 300: '#93c5fd',
44
- 400: '#60a5fa',
45
- 500: '#3b82f6',
46
- 600: '#2563eb',
47
- 700: '#1d4ed8',
48
- },
49
- },
50
- {
51
- key: 'violet',
52
- label: 'Violet',
53
- hex: {
54
- 50: '#f5f3ff',
55
- 100: '#ede9fe',
56
- 200: '#ddd6fe',
57
- 300: '#c4b5fd',
58
- 400: '#a78bfa',
59
- 500: '#8b5cf6',
60
- 600: '#7c3aed',
61
- 700: '#6d28d9',
62
- },
63
- },
64
- {
65
- key: 'emerald',
66
- label: 'Émeraude',
67
- hex: {
68
- 50: '#ecfdf5',
69
- 100: '#d1fae5',
70
- 200: '#a7f3d0',
71
- 300: '#6ee7b7',
72
- 400: '#34d399',
73
- 500: '#10b981',
74
- 600: '#059669',
75
- 700: '#047857',
76
- },
77
- },
78
- {
79
- key: 'rose',
80
- label: 'Rose',
81
- hex: {
82
- 50: '#fff1f2',
83
- 100: '#ffe4e6',
84
- 200: '#fecdd3',
85
- 300: '#fda4af',
86
- 400: '#fb7185',
87
- 500: '#f43f5e',
88
- 600: '#e11d48',
89
- 700: '#be123c',
90
- },
91
- },
92
- {
93
- key: 'cyan',
94
- label: 'Cyan',
95
- hex: {
96
- 50: '#ecfeff',
97
- 100: '#cffafe',
98
- 200: '#a5f3fc',
99
- 300: '#67e8f9',
100
- 400: '#22d3ee',
101
- 500: '#06b6d4',
102
- 600: '#0891b2',
103
- 700: '#0e7490',
104
- },
105
- },
106
- {
107
- key: 'amber',
108
- label: 'Ambre',
109
- hex: {
110
- 50: '#fffbeb',
111
- 100: '#fef3c7',
112
- 200: '#fde68a',
113
- 300: '#fcd34d',
114
- 400: '#fbbf24',
115
- 500: '#f59e0b',
116
- 600: '#d97706',
117
- 700: '#b45309',
118
- },
119
- },
120
- {
121
- key: 'indigo',
122
- label: 'Indigo',
123
- hex: {
124
- 50: '#eef2ff',
125
- 100: '#e0e7ff',
126
- 200: '#c7d2fe',
127
- 300: '#a5b4fc',
128
- 400: '#818cf8',
129
- 500: '#6366f1',
130
- 600: '#4f46e5',
131
- 700: '#4338ca',
132
- },
133
- },
134
- ];
135
-
136
- export const DEFAULT_THEME_KEY = 'orange';
137
-
138
- export function getThemeByKey(key: string): DashboardTheme {
139
- return DASHBOARD_THEMES.find((t) => t.key === key) || DASHBOARD_THEMES[0];
140
- }
@@ -1,14 +0,0 @@
1
- // Layout par défaut pour les nouveaux utilisateurs
2
- // Ce fichier est séparé du widget-registry pour être importable côté serveur
3
- export const DEFAULT_WIDGETS = [
4
- { type: 'stat_total_contacts', x: 0, y: 0, w: 3, h: 2 },
5
- { type: 'stat_new_contacts', x: 3, y: 0, w: 3, h: 2 },
6
- { type: 'stat_completed_tasks', x: 6, y: 0, w: 3, h: 2 },
7
- { type: 'stat_pending_tasks', x: 9, y: 0, w: 3, h: 2 },
8
- { type: 'contacts_chart', x: 0, y: 2, w: 6, h: 4 },
9
- { type: 'top_contacts', x: 6, y: 2, w: 6, h: 4 },
10
- { type: 'activity_chart', x: 0, y: 6, w: 8, h: 4 },
11
- { type: 'tasks_pie', x: 8, y: 6, w: 4, h: 4 },
12
- { type: 'upcoming_tasks', x: 0, y: 10, w: 6, h: 5 },
13
- { type: 'recent_activity', x: 6, y: 10, w: 6, h: 5 },
14
- ];