create-crm-tmp 1.1.3 → 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 (276) hide show
  1. package/bin/create-crm-tmp.js +56 -35
  2. package/package.json +1 -1
  3. package/template/.prettierignore +2 -0
  4. package/template/README.md +230 -115
  5. package/template/components.json +22 -0
  6. package/template/eslint.config.mjs +13 -0
  7. package/template/exemple-contacts.csv +54 -0
  8. package/template/next.config.ts +41 -1
  9. package/template/package.json +63 -15
  10. package/template/prisma/migrations/20260318095700_init_db/migration.sql +978 -0
  11. package/template/prisma/schema.prisma +311 -67
  12. package/template/src/app/(auth)/invite/[token]/page.tsx +28 -29
  13. package/template/src/app/(auth)/layout.tsx +1 -1
  14. package/template/src/app/(auth)/reset-password/complete/page.tsx +21 -27
  15. package/template/src/app/(auth)/reset-password/page.tsx +14 -10
  16. package/template/src/app/(auth)/reset-password/verify/page.tsx +14 -10
  17. package/template/src/app/(auth)/signin/page.tsx +34 -23
  18. package/template/src/app/(dashboard)/agenda/page.tsx +3655 -2357
  19. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
  20. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +609 -338
  21. package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
  22. package/template/src/app/(dashboard)/automatisation/page.tsx +463 -186
  23. package/template/src/app/(dashboard)/closing/page.tsx +517 -469
  24. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6151 -4210
  25. package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1702 -0
  26. package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
  27. package/template/src/app/(dashboard)/contacts/page.tsx +4124 -2130
  28. package/template/src/app/(dashboard)/dashboard/page.tsx +119 -105
  29. package/template/src/app/(dashboard)/dev/page.tsx +1291 -0
  30. package/template/src/app/(dashboard)/error.tsx +37 -0
  31. package/template/src/app/(dashboard)/layout.tsx +6 -2
  32. package/template/src/app/(dashboard)/loading.tsx +5 -0
  33. package/template/src/app/(dashboard)/settings/loading.tsx +19 -0
  34. package/template/src/app/(dashboard)/settings/page.tsx +1773 -3362
  35. package/template/src/app/(dashboard)/templates/page.tsx +504 -303
  36. package/template/src/app/(dashboard)/users/list/page.tsx +364 -355
  37. package/template/src/app/(dashboard)/users/page.tsx +279 -310
  38. package/template/src/app/(dashboard)/users/permissions/page.tsx +104 -99
  39. package/template/src/app/(dashboard)/users/roles/page.tsx +169 -140
  40. package/template/src/app/api/agenda/google-events/route.ts +92 -0
  41. package/template/src/app/api/audit-logs/route.ts +1 -1
  42. package/template/src/app/api/auth/check-active/route.ts +3 -2
  43. package/template/src/app/api/auth/google/callback/route.ts +8 -5
  44. package/template/src/app/api/auth/google/disconnect/route.ts +2 -2
  45. package/template/src/app/api/auth/google/route.ts +2 -1
  46. package/template/src/app/api/auth/google/status/route.ts +7 -31
  47. package/template/src/app/api/companies/[id]/activities/route.ts +129 -0
  48. package/template/src/app/api/companies/[id]/route.ts +194 -0
  49. package/template/src/app/api/companies/export/route.ts +206 -0
  50. package/template/src/app/api/companies/route.ts +196 -0
  51. package/template/src/app/api/contact-views/[id]/pin/route.ts +69 -0
  52. package/template/src/app/api/contact-views/[id]/route.ts +197 -0
  53. package/template/src/app/api/contact-views/route.ts +146 -0
  54. package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +55 -0
  55. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +20 -48
  56. package/template/src/app/api/contacts/[id]/files/route.ts +125 -186
  57. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +27 -1
  58. package/template/src/app/api/contacts/[id]/interactions/route.ts +45 -8
  59. package/template/src/app/api/contacts/[id]/kyc/route.ts +81 -0
  60. package/template/src/app/api/contacts/[id]/meet/route.ts +55 -29
  61. package/template/src/app/api/contacts/[id]/route.ts +184 -21
  62. package/template/src/app/api/contacts/[id]/send-email/route.ts +33 -11
  63. package/template/src/app/api/contacts/[id]/workflows/run/route.ts +67 -0
  64. package/template/src/app/api/contacts/export/route.ts +22 -31
  65. package/template/src/app/api/contacts/import/route.ts +77 -44
  66. package/template/src/app/api/contacts/import-preview/route.ts +139 -0
  67. package/template/src/app/api/contacts/origins/route.ts +63 -0
  68. package/template/src/app/api/contacts/route.ts +322 -57
  69. package/template/src/app/api/cron/cleanup-editor-images/route.ts +166 -0
  70. package/template/src/app/api/dashboard/stats/route.ts +9 -292
  71. package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -3
  72. package/template/src/app/api/dashboard/widgets/route.ts +19 -19
  73. package/template/src/app/api/dev/reminders/test/route.ts +114 -0
  74. package/template/src/app/api/editor/upload-image/route.ts +61 -0
  75. package/template/src/app/api/integrations/google-sheet/jobs/[jobId]/route.ts +47 -0
  76. package/template/src/app/api/integrations/google-sheet/jobs/usage/route.ts +50 -0
  77. package/template/src/app/api/integrations/google-sheet/sync/route.ts +28 -542
  78. package/template/src/app/api/invite/complete/route.ts +20 -23
  79. package/template/src/app/api/jobs/google-sheet/process/route.ts +84 -0
  80. package/template/src/app/api/jobs/google-sheet/schedule/route.ts +50 -0
  81. package/template/src/app/api/reminders/clear/route.ts +120 -0
  82. package/template/src/app/api/reminders/clear/undo/route.ts +112 -0
  83. package/template/src/app/api/reminders/route.ts +165 -39
  84. package/template/src/app/api/reminders/state/route.ts +164 -0
  85. package/template/src/app/api/reset-password/complete/route.ts +11 -13
  86. package/template/src/app/api/reset-password/request/route.ts +1 -1
  87. package/template/src/app/api/reset-password/verify/route.ts +1 -1
  88. package/template/src/app/api/send/route.ts +25 -47
  89. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +10 -21
  90. package/template/src/app/api/settings/closing-reasons/route.ts +10 -21
  91. package/template/src/app/api/settings/company/route.ts +19 -26
  92. package/template/src/app/api/settings/google-ads/[id]/route.ts +20 -23
  93. package/template/src/app/api/settings/google-ads/route.ts +34 -23
  94. package/template/src/app/api/settings/google-calendar/calendars/route.ts +97 -0
  95. package/template/src/app/api/settings/google-calendar/route.ts +124 -0
  96. package/template/src/app/api/settings/google-sheet/[id]/route.ts +48 -23
  97. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +56 -32
  98. package/template/src/app/api/settings/google-sheet/preview/route.ts +110 -0
  99. package/template/src/app/api/settings/google-sheet/route.ts +34 -23
  100. package/template/src/app/api/settings/integrations/logs/route.ts +93 -0
  101. package/template/src/app/api/settings/integrations/notifications/route.ts +67 -0
  102. package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -24
  103. package/template/src/app/api/settings/meta-leads/route.ts +34 -25
  104. package/template/src/app/api/settings/smtp/route.ts +53 -6
  105. package/template/src/app/api/settings/statuses/[id]/route.ts +29 -32
  106. package/template/src/app/api/settings/statuses/route.ts +24 -22
  107. package/template/src/app/api/statuses/route.ts +2 -5
  108. package/template/src/app/api/tasks/[id]/attendees/route.ts +36 -13
  109. package/template/src/app/api/tasks/[id]/route.ts +357 -145
  110. package/template/src/app/api/tasks/meet/route.ts +37 -26
  111. package/template/src/app/api/tasks/route.ts +201 -96
  112. package/template/src/app/api/templates/[id]/route.ts +22 -13
  113. package/template/src/app/api/templates/route.ts +22 -5
  114. package/template/src/app/api/users/[id]/resend-invite/route.ts +95 -0
  115. package/template/src/app/api/users/[id]/route.ts +22 -16
  116. package/template/src/app/api/users/commercials/route.ts +38 -0
  117. package/template/src/app/api/users/for-agenda/route.ts +1 -2
  118. package/template/src/app/api/users/list/route.ts +57 -19
  119. package/template/src/app/api/users/route.ts +89 -34
  120. package/template/src/app/api/webhooks/google-ads/route.ts +40 -1
  121. package/template/src/app/api/webhooks/meta-leads/route.ts +38 -1
  122. package/template/src/app/api/workflows/[id]/route.ts +29 -6
  123. package/template/src/app/api/workflows/process/route.ts +505 -170
  124. package/template/src/app/api/workflows/route.ts +42 -4
  125. package/template/src/app/globals.css +512 -32
  126. package/template/src/app/layout.tsx +28 -9
  127. package/template/src/app/page.tsx +37 -7
  128. package/template/src/components/address-autocomplete.tsx +233 -0
  129. package/template/src/components/config-error-alert.tsx +46 -0
  130. package/template/src/components/contacts/filter-bar.tsx +190 -0
  131. package/template/src/components/contacts/filter-builder.tsx +574 -0
  132. package/template/src/components/contacts/save-view-dialog.tsx +160 -0
  133. package/template/src/components/contacts/views-tab-bar.tsx +449 -0
  134. package/template/src/components/dashboard/activity-chart.tsx +6 -1
  135. package/template/src/components/dashboard/add-widget-dialog.tsx +13 -17
  136. package/template/src/components/dashboard/color-picker.tsx +7 -8
  137. package/template/src/components/dashboard/recent-activity.tsx +2 -5
  138. package/template/src/components/dashboard/stat-card.tsx +1 -3
  139. package/template/src/components/dashboard/status-distribution-chart.tsx +0 -1
  140. package/template/src/components/dashboard/top-contacts-list.tsx +7 -13
  141. package/template/src/components/dashboard/upcoming-tasks-list.tsx +2 -5
  142. package/template/src/components/dashboard/widget-wrapper.tsx +3 -6
  143. package/template/src/components/date-picker.tsx +399 -0
  144. package/template/src/components/editor/upload-editor-image.ts +42 -0
  145. package/template/src/components/editor.tsx +188 -35
  146. package/template/src/components/email-template.tsx +4 -2
  147. package/template/src/components/global-search.tsx +360 -0
  148. package/template/src/components/header.tsx +200 -107
  149. package/template/src/components/inactive-account-guard.tsx +58 -0
  150. package/template/src/components/integration-notifications-listener.tsx +12 -0
  151. package/template/src/components/invitation-email-template.tsx +4 -2
  152. package/template/src/components/lazy-editor.tsx +11 -0
  153. package/template/src/components/meet-cancellation-email-template.tsx +11 -3
  154. package/template/src/components/meet-confirmation-email-template.tsx +10 -3
  155. package/template/src/components/meet-update-email-template.tsx +10 -3
  156. package/template/src/components/page-header.tsx +19 -15
  157. package/template/src/components/protected-page.tsx +94 -0
  158. package/template/src/components/reset-password-email-template.tsx +4 -2
  159. package/template/src/components/settings/integrations/GoogleAdsIntegration.tsx +428 -0
  160. package/template/src/components/settings/integrations/GoogleSheetConfigMonitoringModal.tsx +680 -0
  161. package/template/src/components/settings/integrations/GoogleSheetIntegration.tsx +809 -0
  162. package/template/src/components/settings/integrations/ImportResultDialog.tsx +124 -0
  163. package/template/src/components/settings/integrations/IntegrationLogPanel.tsx +57 -0
  164. package/template/src/components/settings/integrations/IntegrationLogsTable.tsx +186 -0
  165. package/template/src/components/settings/integrations/MetaLeadIntegration.tsx +451 -0
  166. package/template/src/components/sidebar.tsx +117 -100
  167. package/template/src/components/skeleton.tsx +128 -45
  168. package/template/src/components/ui/accordion.tsx +64 -0
  169. package/template/src/components/ui/alert-dialog.tsx +139 -0
  170. package/template/src/components/ui/button.tsx +71 -0
  171. package/template/src/components/ui/components.tsx +1 -1
  172. package/template/src/components/ui/date-picker.tsx +422 -0
  173. package/template/src/components/ui/datetime-picker.tsx +338 -0
  174. package/template/src/components/ui/status-select.tsx +271 -0
  175. package/template/src/components/ui/tooltip.tsx +37 -0
  176. package/template/src/components/view-as-banner.tsx +1 -1
  177. package/template/src/components/view-as-modal.tsx +30 -19
  178. package/template/src/config/nav-pages.ts +108 -0
  179. package/template/src/contexts/app-toast-context.tsx +362 -0
  180. package/template/src/contexts/dashboard-theme-context.tsx +2 -7
  181. package/template/src/contexts/sidebar-context.tsx +27 -53
  182. package/template/src/contexts/task-reminder-context.tsx +134 -160
  183. package/template/src/contexts/view-as-context.tsx +32 -10
  184. package/template/src/hooks/use-alert.tsx +65 -0
  185. package/template/src/hooks/use-confirm.tsx +87 -0
  186. package/template/src/hooks/use-contact-views.ts +140 -0
  187. package/template/src/hooks/use-contacts.ts +69 -0
  188. package/template/src/hooks/use-fetch.ts +17 -0
  189. package/template/src/hooks/use-focus-trap.ts +73 -0
  190. package/template/src/hooks/use-statuses.ts +22 -0
  191. package/template/src/hooks/useIntegrationNotifications.ts +49 -0
  192. package/template/src/lib/address-api.ts +155 -0
  193. package/template/src/lib/auth.ts +8 -1
  194. package/template/src/lib/cache.ts +73 -0
  195. package/template/src/lib/check-permission.ts +12 -177
  196. package/template/src/lib/config-links.ts +14 -0
  197. package/template/src/lib/contact-duplicate.ts +79 -61
  198. package/template/src/lib/contact-interactions.ts +24 -22
  199. package/template/src/lib/contact-view-filters.ts +301 -0
  200. package/template/src/lib/contacts-list-url.ts +190 -0
  201. package/template/src/lib/dashboard-stats.ts +282 -0
  202. package/template/src/lib/dashboard-themes.ts +0 -5
  203. package/template/src/lib/date-utils.ts +176 -0
  204. package/template/src/lib/default-widgets.ts +0 -2
  205. package/template/src/lib/editor-html-image-dimensions.ts +172 -0
  206. package/template/src/lib/editor-image-limits.ts +19 -0
  207. package/template/src/lib/email-html-sanitize.ts +19 -0
  208. package/template/src/lib/encryption.ts +9 -6
  209. package/template/src/lib/fr-geography.ts +192 -0
  210. package/template/src/lib/get-auth-user.ts +25 -0
  211. package/template/src/lib/google-calendar-agenda.ts +201 -0
  212. package/template/src/lib/google-calendar.ts +309 -17
  213. package/template/src/lib/google-fetch.ts +63 -0
  214. package/template/src/lib/google-sheet-sync-jobs.ts +96 -0
  215. package/template/src/lib/google-sheet-sync-runner.ts +514 -0
  216. package/template/src/lib/integration-import-log.ts +21 -0
  217. package/template/src/lib/local-storage.ts +34 -0
  218. package/template/src/lib/permissions.ts +268 -40
  219. package/template/src/lib/prisma.ts +15 -12
  220. package/template/src/lib/qstash.ts +65 -0
  221. package/template/src/lib/reminder-state-server.ts +80 -0
  222. package/template/src/lib/reminder-state.ts +29 -0
  223. package/template/src/lib/roles.ts +12 -15
  224. package/template/src/lib/supabase-storage.ts +113 -0
  225. package/template/src/lib/template-variables.ts +204 -29
  226. package/template/src/lib/utils.ts +71 -11
  227. package/template/src/lib/widget-registry.ts +0 -4
  228. package/template/src/lib/workflow-executor.ts +391 -228
  229. package/template/src/proxy.ts +35 -73
  230. package/template/src/types/contact-views.ts +351 -0
  231. package/template/vercel.json +5 -0
  232. package/template/WORKFLOWS_CRON.md +0 -185
  233. package/template/prisma/migrations/20251126144728_init/migration.sql +0 -78
  234. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +0 -5
  235. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +0 -19
  236. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +0 -22
  237. package/template/prisma/migrations/20251128132303_add_status/migration.sql +0 -23
  238. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +0 -75
  239. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +0 -2
  240. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +0 -45
  241. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +0 -2
  242. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +0 -27
  243. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +0 -20
  244. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +0 -18
  245. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +0 -32
  246. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +0 -20
  247. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +0 -12
  248. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +0 -21
  249. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +0 -11
  250. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +0 -12
  251. package/template/prisma/migrations/20251208094843_mg/migration.sql +0 -14
  252. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +0 -14
  253. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +0 -26
  254. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +0 -2
  255. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +0 -2
  256. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +0 -2
  257. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +0 -3
  258. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +0 -21
  259. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +0 -2
  260. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +0 -10
  261. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +0 -26
  262. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +0 -24
  263. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +0 -11
  264. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +0 -12
  265. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +0 -25
  266. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +0 -8
  267. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +0 -2
  268. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +0 -80
  269. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +0 -32
  270. package/template/prisma/migrations/20251220000000_add_task_interaction_type/migration.sql +0 -4
  271. package/template/prisma/migrations/20251221000000_add_task_type/migration.sql +0 -3
  272. package/template/prisma/migrations/20251221000001_add_event_color/migration.sql +0 -23
  273. package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +0 -20
  274. package/template/prisma/migrations/20260226093949_fix_cascade_on_user_delete/migration.sql +0 -69
  275. package/template/src/app/(dashboard)/users/layout.tsx +0 -30
  276. package/template/src/lib/google-drive.ts +0 -380
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useEffect, useState, useCallback, useRef, useMemo } from 'react';
4
- import { GridLayout, useContainerWidth, type Layout, type LayoutItem } from 'react-grid-layout';
4
+ import dynamic from 'next/dynamic';
5
+ import { useContainerWidth, type Layout, type LayoutItem } from 'react-grid-layout';
5
6
  import { Plus, LayoutDashboard, RotateCcw, ShieldAlert } from 'lucide-react';
6
7
  import { PageHeader } from '@/components/page-header';
7
8
  import { WidgetWrapper } from '@/components/dashboard/widget-wrapper';
@@ -9,16 +10,55 @@ import { AddWidgetDialog } from '@/components/dashboard/add-widget-dialog';
9
10
  import { DashboardColorPicker } from '@/components/dashboard/color-picker';
10
11
  import { useUserRole } from '@/hooks/use-user-role';
11
12
  import { DashboardThemeProvider, useDashboardTheme } from '@/contexts/dashboard-theme-context';
12
- import { StatCard } from '@/components/dashboard/stat-card';
13
- import { ContactsChart } from '@/components/dashboard/contacts-chart';
14
- import { ActivityChart } from '@/components/dashboard/activity-chart';
15
- import { StatusDistributionChart } from '@/components/dashboard/status-distribution-chart';
16
- import { TasksPieChart } from '@/components/dashboard/tasks-pie-chart';
17
- import { UpcomingTasksList } from '@/components/dashboard/upcoming-tasks-list';
18
- import { RecentActivity } from '@/components/dashboard/recent-activity';
19
- import { TopContactsList } from '@/components/dashboard/top-contacts-list';
20
- import { InteractionsByTypeChart } from '@/components/dashboard/interactions-by-type-chart';
21
13
  import { getWidgetDefinition } from '@/lib/widget-registry';
14
+ import { useAppToast } from '@/contexts/app-toast-context';
15
+ import { devToast } from '@/lib/utils';
16
+
17
+ const GridLayout = dynamic(() => import('react-grid-layout').then((mod) => mod.GridLayout), {
18
+ ssr: false,
19
+ });
20
+ const StatCard = dynamic(
21
+ () => import('@/components/dashboard/stat-card').then((mod) => mod.StatCard),
22
+ { ssr: false },
23
+ );
24
+ const ContactsChart = dynamic(
25
+ () => import('@/components/dashboard/contacts-chart').then((mod) => mod.ContactsChart),
26
+ { ssr: false },
27
+ );
28
+ const ActivityChart = dynamic(
29
+ () => import('@/components/dashboard/activity-chart').then((mod) => mod.ActivityChart),
30
+ { ssr: false },
31
+ );
32
+ const StatusDistributionChart = dynamic(
33
+ () =>
34
+ import('@/components/dashboard/status-distribution-chart').then(
35
+ (mod) => mod.StatusDistributionChart,
36
+ ),
37
+ { ssr: false },
38
+ );
39
+ const TasksPieChart = dynamic(
40
+ () => import('@/components/dashboard/tasks-pie-chart').then((mod) => mod.TasksPieChart),
41
+ { ssr: false },
42
+ );
43
+ const UpcomingTasksList = dynamic(
44
+ () => import('@/components/dashboard/upcoming-tasks-list').then((mod) => mod.UpcomingTasksList),
45
+ { ssr: false },
46
+ );
47
+ const RecentActivity = dynamic(
48
+ () => import('@/components/dashboard/recent-activity').then((mod) => mod.RecentActivity),
49
+ { ssr: false },
50
+ );
51
+ const TopContactsList = dynamic(
52
+ () => import('@/components/dashboard/top-contacts-list').then((mod) => mod.TopContactsList),
53
+ { ssr: false },
54
+ );
55
+ const InteractionsByTypeChart = dynamic(
56
+ () =>
57
+ import('@/components/dashboard/interactions-by-type-chart').then(
58
+ (mod) => mod.InteractionsByTypeChart,
59
+ ),
60
+ { ssr: false },
61
+ );
22
62
 
23
63
  interface DashboardWidget {
24
64
  id: string;
@@ -89,6 +129,7 @@ export default function DashboardPage() {
89
129
  }
90
130
 
91
131
  function DashboardContent() {
132
+ const toast = useAppToast();
92
133
  const [widgets, setWidgets] = useState<DashboardWidget[]>([]);
93
134
  const [stats, setStats] = useState<DashboardStats | null>(null);
94
135
  const [loading, setLoading] = useState(true);
@@ -98,7 +139,6 @@ function DashboardContent() {
98
139
  const { hasPermission, isLoading: permissionsLoading } = useUserRole();
99
140
  const { theme } = useDashboardTheme();
100
141
 
101
- // Permissions dashboard
102
142
  const canViewDashboard = hasPermission('dashboard.view');
103
143
  const canManageWidgets = hasPermission('dashboard.widgets.manage');
104
144
  const canResetDashboard = hasPermission('dashboard.widgets.reset');
@@ -108,47 +148,30 @@ function DashboardContent() {
108
148
  mounted,
109
149
  } = useContainerWidth({ initialWidth: 1200 });
110
150
 
111
- // Nombre de colonnes dynamique basé sur la largeur réelle
112
151
  const cols = useMemo(() => {
113
152
  if (containerWidth < 768) return 2;
114
153
  return 12;
115
154
  }, [containerWidth]);
116
155
 
117
- // Charger widgets et stats
118
156
  useEffect(() => {
119
157
  async function fetchData() {
120
158
  try {
121
- const widgetsRes = await fetch('/api/dashboard/widgets');
122
- let widgetsData: DashboardWidget[] = [];
159
+ const [widgetsRes, statsRes] = await Promise.all([
160
+ fetch('/api/dashboard/widgets'),
161
+ fetch('/api/dashboard/stats'),
162
+ ]);
123
163
 
124
164
  if (widgetsRes.ok) {
125
- widgetsData = await widgetsRes.json();
165
+ setWidgets(await widgetsRes.json());
126
166
  }
127
167
 
128
- // Initialiser le layout par défaut uniquement à la toute première visite
129
- // (pas de widgets ET jamais initialisé auparavant)
130
- const hasBeenInitialized = localStorage.getItem('dashboard_initialized');
131
- if (widgetsData.length === 0 && !hasBeenInitialized) {
132
- const initRes = await fetch('/api/dashboard/widgets', {
133
- method: 'POST',
134
- headers: { 'Content-Type': 'application/json' },
135
- body: JSON.stringify({ initDefault: true }),
136
- });
137
- if (initRes.ok) {
138
- widgetsData = await initRes.json();
139
- }
140
- localStorage.setItem('dashboard_initialized', 'true');
141
- }
142
-
143
- setWidgets(widgetsData);
144
-
145
- const statsRes = await fetch('/api/dashboard/stats');
146
168
  if (statsRes.ok) {
147
- const statsData = await statsRes.json();
148
- setStats(statsData);
169
+ setStats(await statsRes.json());
149
170
  }
150
171
  } catch (err) {
151
- setError(err instanceof Error ? err.message : 'Une erreur est survenue');
172
+ const message = err instanceof Error ? err.message : 'Une erreur est survenue';
173
+ setError(message);
174
+ toast.error(devToast('Une erreur est survenue lors du chargement du tableau de bord', err));
152
175
  } finally {
153
176
  setLoading(false);
154
177
  }
@@ -157,7 +180,6 @@ function DashboardContent() {
157
180
  fetchData();
158
181
  }, []);
159
182
 
160
- // Sauvegarder les positions avec debounce
161
183
  const saveLayout = useCallback((updatedWidgets: DashboardWidget[]) => {
162
184
  if (saveTimeoutRef.current) {
163
185
  clearTimeout(saveTimeoutRef.current);
@@ -180,13 +202,11 @@ function DashboardContent() {
180
202
  });
181
203
  } catch (err) {
182
204
  console.error('Erreur sauvegarde layout:', err);
205
+ toast.error(devToast('Impossible de sauvegarder la disposition du tableau de bord. Veuillez réessayer.', err));
183
206
  }
184
207
  }, 500);
185
208
  }, []);
186
209
 
187
- // Gérer le changement de layout (drag/resize)
188
- // Ne sauvegarde en base que quand on est en mode desktop (12 cols)
189
- // pour ne pas écraser les positions desktop avec les positions mobile
190
210
  const handleLayoutChange = useCallback(
191
211
  (layout: Layout) => {
192
212
  if (cols !== 12) return;
@@ -211,7 +231,6 @@ function DashboardContent() {
211
231
  [widgets, saveLayout, cols],
212
232
  );
213
233
 
214
- // Ajouter un widget
215
234
  const handleAddWidget = useCallback(async (type: string, w: number, h: number) => {
216
235
  try {
217
236
  const res = await fetch('/api/dashboard/widgets', {
@@ -226,12 +245,12 @@ function DashboardContent() {
226
245
  }
227
246
  } catch (err) {
228
247
  console.error('Erreur ajout widget:', err);
248
+ toast.error(devToast('Impossible d\'ajouter le widget. Veuillez réessayer.', err));
229
249
  }
230
250
 
231
251
  setShowAddDialog(false);
232
252
  }, []);
233
253
 
234
- // Supprimer un widget
235
254
  const handleRemoveWidget = useCallback(async (widgetId: string) => {
236
255
  try {
237
256
  const res = await fetch(`/api/dashboard/widgets/${widgetId}`, {
@@ -243,40 +262,32 @@ function DashboardContent() {
243
262
  }
244
263
  } catch (err) {
245
264
  console.error('Erreur suppression widget:', err);
265
+ toast.error(devToast('Impossible de retirer le widget. Veuillez réessayer.', err));
246
266
  }
247
267
  }, []);
248
268
 
249
- // Réinitialiser le layout par défaut
250
269
  const handleResetLayout = useCallback(async () => {
251
270
  try {
252
- // Supprimer tous les widgets existants
253
- await Promise.all(
254
- widgets.map((w) => fetch(`/api/dashboard/widgets/${w.id}`, { method: 'DELETE' })),
255
- );
256
-
257
- // Recréer le layout par défaut
258
- const initRes = await fetch('/api/dashboard/widgets', {
271
+ const res = await fetch('/api/dashboard/widgets', {
259
272
  method: 'POST',
260
273
  headers: { 'Content-Type': 'application/json' },
261
274
  body: JSON.stringify({ initDefault: true }),
262
275
  });
263
276
 
264
- if (initRes.ok) {
265
- const newWidgets = await initRes.json();
266
- setWidgets(newWidgets);
277
+ if (res.ok) {
278
+ setWidgets(await res.json());
267
279
  }
268
280
  } catch (err) {
269
281
  console.error('Erreur réinitialisation layout:', err);
282
+ toast.error(devToast('Impossible de réinitialiser le tableau de bord. Veuillez réessayer.', err));
270
283
  }
271
- }, [widgets]);
284
+ }, []);
272
285
 
273
- // Construire le layout adapté au nombre de colonnes actuel
274
286
  const gridLayout = useMemo((): Layout => {
275
287
  return widgets.map((widget) => {
276
288
  const def = getWidgetDefinition(widget.type);
277
289
 
278
290
  if (cols === 2) {
279
- // Mobile : stat cards (w <= 3 en 12 cols) → 1 col, le reste → 2 cols (pleine largeur)
280
291
  const isSmallWidget = widget.w <= 4;
281
292
  const mobileW = isSmallWidget ? 1 : 2;
282
293
  return {
@@ -291,7 +302,6 @@ function DashboardContent() {
291
302
  };
292
303
  }
293
304
 
294
- // Desktop : layout stocké en base (grille 12 colonnes)
295
305
  return {
296
306
  i: widget.id,
297
307
  x: widget.x,
@@ -305,7 +315,6 @@ function DashboardContent() {
305
315
  });
306
316
  }, [widgets, cols]);
307
317
 
308
- // Rendu du contenu d'un widget selon son type
309
318
  const renderWidgetContent = useCallback(
310
319
  (widget: DashboardWidget) => {
311
320
  if (!stats) return null;
@@ -377,7 +386,6 @@ function DashboardContent() {
377
386
  [stats],
378
387
  );
379
388
 
380
- // Contenu intérieur selon l'état
381
389
  const renderContent = () => {
382
390
  if (loading || permissionsLoading) {
383
391
  return (
@@ -398,11 +406,12 @@ function DashboardContent() {
398
406
 
399
407
  if (!canViewDashboard) {
400
408
  return (
401
- <div className="flex h-96 flex-col items-center justify-center rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50">
409
+ <div className="ui-fade-in flex h-96 flex-col items-center justify-center rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50">
402
410
  <ShieldAlert className="h-12 w-12 text-gray-300" />
403
411
  <h3 className="mt-4 text-base font-medium text-gray-600">Accès restreint</h3>
404
412
  <p className="mt-1 text-center text-sm text-gray-400">
405
- Vous n&apos;avez pas la permission d&apos;accéder au tableau de bord.<br />
413
+ Vous n&apos;avez pas la permission d&apos;accéder au tableau de bord.
414
+ <br />
406
415
  Contactez votre administrateur pour obtenir les droits nécessaires.
407
416
  </p>
408
417
  </div>
@@ -410,17 +419,12 @@ function DashboardContent() {
410
419
  }
411
420
 
412
421
  if (error) {
413
- return (
414
- <div className="flex h-96 items-center justify-center p-4">
415
- <p className="text-sm text-red-500">{error}</p>
416
- </div>
417
- );
422
+ return null;
418
423
  }
419
424
 
420
425
  return null;
421
426
  };
422
427
 
423
- // CSS variables pour le thème
424
428
  const themeVars = {
425
429
  '--dash-50': theme.hex[50],
426
430
  '--dash-100': theme.hex[100],
@@ -444,7 +448,7 @@ function DashboardContent() {
444
448
  {widgets.length > 0 && canResetDashboard && (
445
449
  <button
446
450
  onClick={handleResetLayout}
447
- className="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-600 shadow-sm transition-all duration-150 hover:bg-gray-50 hover:text-gray-900 hover:shadow-md active:scale-[0.98]"
451
+ className="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-600 shadow-sm transition-[background-color,color,box-shadow,transform] duration-150 hover:bg-gray-50 hover:text-gray-900 hover:shadow-md active:scale-[0.98]"
448
452
  >
449
453
  <RotateCcw className="h-4 w-4" />
450
454
  Réinitialiser
@@ -453,7 +457,7 @@ function DashboardContent() {
453
457
  {canManageWidgets && (
454
458
  <button
455
459
  onClick={() => setShowAddDialog(true)}
456
- className="dash-btn inline-flex cursor-pointer items-center gap-2 rounded-xl px-4 py-2.5 text-sm font-medium shadow-sm transition-all duration-150 hover:shadow-md active:scale-[0.98]"
460
+ className="dash-btn inline-flex cursor-pointer items-center gap-2 rounded-xl px-4 py-2.5 text-sm font-medium shadow-sm transition-[box-shadow,transform] duration-150 hover:shadow-md active:scale-[0.98]"
457
461
  >
458
462
  <Plus className="h-4 w-4" />
459
463
  Ajouter un Widget
@@ -464,17 +468,16 @@ function DashboardContent() {
464
468
  }
465
469
  />
466
470
 
467
- {/* Le ref est TOUJOURS dans le DOM pour que useContainerWidth mesure correctement */}
468
471
  <div ref={containerRef} className="p-4 sm:p-6">
469
472
  {renderContent()}
470
473
  {!loading && !permissionsLoading && !error && canViewDashboard && widgets.length === 0 && (
471
- <div className="flex h-96 flex-col items-center justify-center rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50">
474
+ <div className="ui-fade-in flex h-96 flex-col items-center justify-center rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50">
472
475
  <LayoutDashboard className="h-12 w-12 text-gray-300" />
473
476
  <h3 className="mt-4 text-base font-medium text-gray-600">Aucun widget configuré</h3>
474
477
  <p className="mt-1 text-sm text-gray-400">
475
478
  {canManageWidgets
476
479
  ? 'Ajoutez des widgets pour personnaliser votre tableau de bord'
477
- : 'Aucun widget n\'est configuré. Contactez votre administrateur.'}
480
+ : "Aucun widget n'est configuré. Contactez votre administrateur."}
478
481
  </p>
479
482
  {canManageWidgets && (
480
483
  <button
@@ -488,38 +491,49 @@ function DashboardContent() {
488
491
  </div>
489
492
  )}
490
493
 
491
- {!loading && !permissionsLoading && !error && canViewDashboard && widgets.length > 0 && mounted && (
492
- <GridLayout
493
- className="layout"
494
- width={containerWidth}
495
- layout={gridLayout}
496
- gridConfig={{
497
- cols,
498
- rowHeight: 60,
499
- margin: [16, 16] as const,
500
- containerPadding: [0, 0] as const,
501
- maxRows: Infinity,
502
- }}
503
- onLayoutChange={handleLayoutChange}
504
- dragConfig={{
505
- handle: '.drag-handle',
506
- enabled: canManageWidgets,
507
- threshold: 3,
508
- bounded: false,
509
- }}
510
- resizeConfig={{ enabled: canManageWidgets, handles: ['se'] }}
511
- >
512
- {widgets.map((widget) => (
513
- <div key={widget.id}>
514
- <WidgetWrapper
515
- onRemove={canManageWidgets ? () => { handleRemoveWidget(widget.id); } : undefined}
516
- >
517
- {renderWidgetContent(widget)}
518
- </WidgetWrapper>
519
- </div>
520
- ))}
521
- </GridLayout>
522
- )}
494
+ {!loading &&
495
+ !permissionsLoading &&
496
+ !error &&
497
+ canViewDashboard &&
498
+ widgets.length > 0 &&
499
+ mounted && (
500
+ <GridLayout
501
+ className="layout ui-fade-in"
502
+ width={containerWidth}
503
+ layout={gridLayout}
504
+ gridConfig={{
505
+ cols,
506
+ rowHeight: 60,
507
+ margin: [16, 16] as const,
508
+ containerPadding: [0, 0] as const,
509
+ maxRows: Infinity,
510
+ }}
511
+ onLayoutChange={handleLayoutChange}
512
+ dragConfig={{
513
+ handle: '.drag-handle',
514
+ enabled: canManageWidgets,
515
+ threshold: 3,
516
+ bounded: false,
517
+ }}
518
+ resizeConfig={{ enabled: canManageWidgets, handles: ['se'] }}
519
+ >
520
+ {widgets.map((widget) => (
521
+ <div key={widget.id}>
522
+ <WidgetWrapper
523
+ onRemove={
524
+ canManageWidgets
525
+ ? () => {
526
+ handleRemoveWidget(widget.id);
527
+ }
528
+ : undefined
529
+ }
530
+ >
531
+ {renderWidgetContent(widget)}
532
+ </WidgetWrapper>
533
+ </div>
534
+ ))}
535
+ </GridLayout>
536
+ )}
523
537
  </div>
524
538
 
525
539
  <AddWidgetDialog