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,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { useEffect, useState } from 'react';
3
+ import React, { useMemo } from 'react';
4
4
  import Link from 'next/link';
5
5
  import {
6
6
  Users,
@@ -13,6 +13,8 @@ import {
13
13
  ShieldX,
14
14
  } from 'lucide-react';
15
15
  import { PERMISSIONS } from '@/lib/permissions';
16
+ import { ProtectedPage } from '@/components/protected-page';
17
+ import { useFetch } from '@/hooks/use-fetch';
16
18
 
17
19
  interface Stats {
18
20
  activeUsers: number;
@@ -26,7 +28,7 @@ interface AuditLog {
26
28
  action: string;
27
29
  entityType: string;
28
30
  entityId?: string | null;
29
- metadata?: any;
31
+ metadata?: Record<string, unknown>;
30
32
  createdAt: string;
31
33
  actor?: {
32
34
  id: string;
@@ -40,74 +42,31 @@ interface AuditLog {
40
42
  } | null;
41
43
  }
42
44
 
43
- export default function AccessControlPage() {
44
- const [stats, setStats] = useState<Stats>({
45
- activeUsers: 0,
46
- totalUsers: 0,
47
- rolesCount: 0,
48
- permissionsCount: PERMISSIONS.length,
49
- });
50
- const [loading, setLoading] = useState(true);
51
- const [auditLogs, setAuditLogs] = useState<AuditLog[]>([]);
52
- const [auditLoading, setAuditLoading] = useState(true);
53
-
54
- useEffect(() => {
55
- async function fetchStats() {
56
- try {
57
- const [usersResponse, rolesResponse] = await Promise.all([
58
- fetch('/api/users'),
59
- fetch('/api/roles'),
60
- ]);
61
-
62
- if (usersResponse.ok) {
63
- const users = await usersResponse.json();
64
- setStats((prev) => ({
65
- ...prev,
66
- activeUsers: users.filter((u: { active: boolean }) => u.active).length,
67
- totalUsers: users.length,
68
- }));
69
- }
70
-
71
- if (rolesResponse.ok) {
72
- const roles = await rolesResponse.json();
73
- setStats((prev) => ({
74
- ...prev,
75
- rolesCount: roles.length,
76
- }));
77
- }
78
- } catch (error) {
79
- console.error('Erreur lors du chargement des stats:', error);
80
- } finally {
81
- setLoading(false);
82
- }
83
- }
45
+ interface UserSummary {
46
+ active: boolean;
47
+ }
84
48
 
85
- fetchStats();
86
- }, []);
49
+ interface RoleSummary {
50
+ id: string;
51
+ }
87
52
 
88
- useEffect(() => {
89
- async function fetchAuditLogs() {
90
- try {
91
- setAuditLoading(true);
92
- const response = await fetch('/api/audit-logs?limit=10');
93
- if (response.ok) {
94
- const data = await response.json();
95
- setAuditLogs(
96
- data.map((log: any) => ({
97
- ...log,
98
- createdAt: log.createdAt,
99
- })),
100
- );
101
- }
102
- } catch (error) {
103
- console.error('Erreur lors du chargement des logs d’audit:', error);
104
- } finally {
105
- setAuditLoading(false);
106
- }
107
- }
53
+ export default function AccessControlPage() {
54
+ const { data: users = [], isLoading: usersLoading } = useFetch<UserSummary[]>('/api/users');
55
+ const { data: roles = [], isLoading: rolesLoading } = useFetch<RoleSummary[]>('/api/roles');
56
+ const { data: auditLogs = [], isLoading: auditLoading } = useFetch<AuditLog[]>(
57
+ '/api/audit-logs?limit=10',
58
+ );
108
59
 
109
- fetchAuditLogs();
110
- }, []);
60
+ const stats = useMemo<Stats>(
61
+ () => ({
62
+ activeUsers: users.filter((u) => u.active).length,
63
+ totalUsers: users.length,
64
+ rolesCount: roles.length,
65
+ permissionsCount: PERMISSIONS.length,
66
+ }),
67
+ [roles.length, users],
68
+ );
69
+ const loading = usersLoading || rolesLoading;
111
70
 
112
71
  const cards = [
113
72
  {
@@ -149,276 +108,286 @@ export default function AccessControlPage() {
149
108
  ];
150
109
 
151
110
  return (
152
- <div className="h-full">
153
- <div className="border-b border-gray-200 bg-white">
154
- <div className="p-4 sm:p-6">
155
- <h1 className="text-2xl font-bold text-gray-900">Droits d&apos;accès</h1>
156
- <p className="mt-1 text-sm text-gray-500">
157
- Gérer les utilisateurs, profils et permissions de votre système
158
- </p>
111
+ <ProtectedPage requiredPermission="users.view">
112
+ <div className="h-full">
113
+ <div className="border-b border-gray-200 bg-white">
114
+ <div className="flex gap-3 p-4 sm:p-6">
115
+ <div>
116
+ <h1 className="text-2xl font-bold text-gray-900">Droits d&apos;accès</h1>
117
+ <p className="mt-1 text-sm text-gray-500">
118
+ Gérer les utilisateurs, profils et permissions de votre système
119
+ </p>
120
+ </div>
121
+ </div>
159
122
  </div>
160
- </div>
161
123
 
162
- <div className="space-y-8 p-4 sm:p-6">
163
- <div className="grid gap-6 lg:grid-cols-3">
164
- {cards.map((card) => {
165
- const Icon = card.icon;
166
- return (
167
- <Link
168
- key={card.title}
169
- href={card.href}
170
- className="group cursor-pointer rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-all hover:border-indigo-300 hover:shadow-md"
171
- >
172
- <div className="flex items-start gap-4">
173
- <div className={`rounded-lg p-3 ${card.iconBg}`}>
174
- <Icon className={`h-6 w-6 ${card.iconColor}`} />
175
- </div>
176
- <div className="flex-1">
177
- <h3 className="text-lg font-semibold text-gray-900 group-hover:text-indigo-600">
178
- {card.title}
179
- </h3>
180
- <p className="text-sm font-medium text-gray-600">{card.subtitle}</p>
124
+ <div className="space-y-8 p-4 sm:p-6">
125
+ <div className="grid gap-6 lg:grid-cols-3">
126
+ {cards.map((card) => {
127
+ const Icon = card.icon;
128
+ return (
129
+ <Link
130
+ key={card.title}
131
+ href={card.href}
132
+ className="group cursor-pointer rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-all hover:border-blue-300 hover:shadow-md"
133
+ >
134
+ <div className="flex items-start gap-4">
135
+ <div className={`rounded-lg p-3 ${card.iconBg}`}>
136
+ <Icon className={`h-6 w-6 ${card.iconColor}`} />
137
+ </div>
138
+ <div className="flex-1">
139
+ <h3 className="text-lg font-semibold text-gray-900 group-hover:text-blue-600">
140
+ {card.title}
141
+ </h3>
142
+ <p className="text-sm font-medium text-gray-600">{card.subtitle}</p>
143
+ </div>
181
144
  </div>
182
- </div>
183
145
 
184
- <p className="mt-4 text-sm text-gray-600">{card.description}</p>
146
+ <p className="mt-4 text-sm text-gray-600">{card.description}</p>
185
147
 
186
- <div className="mt-6 flex items-baseline gap-2">
187
- <span className="text-2xl font-bold text-gray-900">{card.stat}</span>
188
- <span className="text-sm text-gray-500">{card.statLabel}</span>
189
- </div>
190
- </Link>
191
- );
192
- })}
193
- </div>
148
+ <div className="mt-6 flex items-baseline gap-2">
149
+ <span className="text-2xl font-bold text-gray-900">{card.stat}</span>
150
+ <span className="text-sm text-gray-500">{card.statLabel}</span>
151
+ </div>
152
+ </Link>
153
+ );
154
+ })}
155
+ </div>
194
156
 
195
- {/* Historique des modifications récentes */}
196
- <div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
197
- <div className="mb-4 flex items-center justify-between">
198
- <div>
199
- <h2 className="text-base font-semibold text-gray-900">
200
- Historique des modifications récentes
201
- </h2>
202
- <p className="text-sm text-gray-500">
203
- Les 10 dernières actions effectuées sur les utilisateurs et profils
204
- </p>
157
+ {/* Historique des modifications récentes */}
158
+ <div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
159
+ <div className="mb-4 flex items-center justify-between">
160
+ <div>
161
+ <h2 className="text-base font-semibold text-gray-900">
162
+ Historique des modifications récentes
163
+ </h2>
164
+ <p className="text-sm text-gray-500">
165
+ Les 10 dernières actions effectuées sur les utilisateurs et profils
166
+ </p>
167
+ </div>
205
168
  </div>
206
- </div>
207
169
 
208
- {auditLoading ? (
209
- <p className="text-sm text-gray-500">Chargement de l’historique...</p>
210
- ) : auditLogs.length === 0 ? (
211
- <p className="text-sm text-gray-500">Aucune action récente.</p>
212
- ) : (
213
- <ul className="divide-y divide-gray-100">
214
- {auditLogs.map((log) => {
215
- const actorLabel = log.actor?.name || log.actor?.email || 'Système';
216
- const dateLabel = new Date(log.createdAt).toLocaleString('fr-FR', {
217
- day: '2-digit',
218
- month: 'short',
219
- year: 'numeric',
220
- hour: '2-digit',
221
- minute: '2-digit',
222
- });
170
+ {auditLoading ? (
171
+ <p className="text-sm text-gray-500">Chargement de l’historique...</p>
172
+ ) : auditLogs.length === 0 ? (
173
+ <p className="text-sm text-gray-500">Aucune action récente.</p>
174
+ ) : (
175
+ <ul className="divide-y divide-gray-100">
176
+ {auditLogs.map((log) => {
177
+ const actorLabel = log.actor?.name || log.actor?.email || 'Système';
178
+ const dateLabel = new Date(log.createdAt).toLocaleString('fr-FR', {
179
+ day: '2-digit',
180
+ month: 'short',
181
+ year: 'numeric',
182
+ hour: '2-digit',
183
+ minute: '2-digit',
184
+ });
223
185
 
224
- // Traduction des actions et sélection de l'icône
225
- let title = '';
226
- let subtitle = '';
227
- let details: React.ReactElement[] = [];
228
- let IconComponent: React.ComponentType<{ className?: string }> = Shield;
186
+ // Traduction des actions et sélection de l'icône
187
+ let title = '';
188
+ let subtitle = '';
189
+ let details: React.ReactElement[] = [];
190
+ let IconComponent: React.ComponentType<{ className?: string }> = Shield;
229
191
 
230
- if (log.action === 'USER_CREATED') {
231
- title = `${actorLabel} • Ajout Utilisateur`;
232
- IconComponent = UserPlus;
233
- const metadata = log.metadata as {
234
- name?: string;
235
- email?: string;
236
- customRoleId?: string;
237
- customRoleName?: string;
238
- wasExisting?: boolean;
239
- };
240
- if (metadata?.name) {
241
- subtitle = metadata.name;
242
- }
243
- details = [
244
- metadata?.email && (
245
- <li key="email" className="text-xs text-gray-600">
246
- <span className="font-medium">Email :</span> {metadata.email}
247
- </li>
248
- ),
249
- metadata?.name && (
250
- <li key="name" className="text-xs text-gray-600">
251
- <span className="font-medium">Nom :</span> {metadata.name}
252
- </li>
253
- ),
254
- metadata?.customRoleName && (
255
- <li key="profil" className="text-xs text-gray-600">
256
- <span className="font-medium">Profil :</span> {metadata.customRoleName}
257
- </li>
258
- ),
259
- ].filter(Boolean) as React.ReactElement[];
260
- } else if (log.action === 'USER_UPDATED') {
261
- title = `${actorLabel} • Modification Utilisateur`;
262
- IconComponent = UserCog;
263
- if (log.targetUser?.name) {
264
- subtitle = log.targetUser.name;
265
- }
266
- const changes = log.metadata?.changes as
267
- | Record<string, { old: any; new: any }>
268
- | undefined;
269
- if (changes) {
270
- const fieldLabels: Record<string, string> = {
271
- name: 'Nom',
272
- email: 'Email',
273
- customRoleId: 'Profil',
274
- profil: 'Profil',
275
- active: 'Statut',
276
- phone: 'Téléphone',
277
- companyId: 'Société',
278
- folderView: 'Vue des dossiers',
279
- viewUnassignedContacts: 'Voir contacts non attribués',
192
+ if (log.action === 'USER_CREATED') {
193
+ title = `${actorLabel} • Ajout Utilisateur`;
194
+ IconComponent = UserPlus;
195
+ const metadata = log.metadata as {
196
+ name?: string;
197
+ email?: string;
198
+ customRoleId?: string;
199
+ customRoleName?: string;
200
+ wasExisting?: boolean;
280
201
  };
281
- details = Object.entries(changes).map(([field, value]) => {
282
- const label = fieldLabels[field] || field;
283
- const oldValue =
284
- value.old === null || value.old === undefined || value.old === ''
285
- ? 'vide'
286
- : String(value.old);
287
- const newValue =
288
- value.new === null || value.new === undefined || value.new === ''
289
- ? 'vide'
290
- : String(value.new);
291
- return (
292
- <li key={field} className="text-xs text-gray-600">
293
- <span className="font-medium">{label} :</span>{' '}
294
- <span className="text-red-500 line-through">{oldValue}</span>{' '}
295
- <span className="text-green-600">→ {newValue}</span>
202
+ if (metadata?.name) {
203
+ subtitle = metadata.name;
204
+ }
205
+ details = [
206
+ metadata?.email && (
207
+ <li key="email" className="text-xs text-gray-600">
208
+ <span className="font-medium">Email :</span> {metadata.email}
296
209
  </li>
297
- );
298
- });
299
- }
300
- } else if (log.action === 'ROLE_CREATED') {
301
- title = `${actorLabel} • Ajout Profil`;
302
- IconComponent = ShieldPlus;
303
- const metadata = log.metadata as {
304
- name?: string;
305
- description?: string;
306
- permissions?: string[];
307
- };
308
- if (metadata?.name) {
309
- subtitle = metadata.name;
310
- }
311
- details = [
312
- metadata?.description && (
313
- <li key="description" className="text-xs text-gray-600">
314
- <span className="font-medium">Description :</span> {metadata.description}
315
- </li>
316
- ),
317
- metadata?.permissions && (
318
- <li key="permissions" className="text-xs text-gray-600">
319
- <span className="font-medium">Permissions :</span>{' '}
320
- {metadata.permissions.length} permission
321
- {metadata.permissions.length > 1 ? 's' : ''}
322
- </li>
323
- ),
324
- ].filter(Boolean) as React.ReactElement[];
325
- } else if (log.action === 'ROLE_UPDATED') {
326
- title = `${actorLabel} • Modification Profil`;
327
- IconComponent = ShieldCheck;
328
- const metadata = log.metadata as {
329
- before?: { name?: string; description?: string; permissions?: string[] };
330
- after?: { name?: string; description?: string; permissions?: string[] };
331
- };
332
- if (metadata?.after?.name) {
333
- subtitle = metadata.after.name;
334
- }
335
- if (metadata?.before && metadata?.after) {
336
- const before = metadata.before;
337
- const after = metadata.after;
338
- if (before.name !== after.name) {
339
- details.push(
210
+ ),
211
+ metadata?.name && (
340
212
  <li key="name" className="text-xs text-gray-600">
341
- <span className="font-medium">Nom :</span>{' '}
342
- <span className="text-red-500 line-through">{before.name || 'vide'}</span>{' '}
343
- <span className="text-green-600">→ {after.name || 'vide'}</span>
344
- </li>,
345
- );
213
+ <span className="font-medium">Nom :</span> {metadata.name}
214
+ </li>
215
+ ),
216
+ metadata?.customRoleName && (
217
+ <li key="profil" className="text-xs text-gray-600">
218
+ <span className="font-medium">Profil :</span> {metadata.customRoleName}
219
+ </li>
220
+ ),
221
+ ].filter(Boolean) as React.ReactElement[];
222
+ } else if (log.action === 'USER_UPDATED') {
223
+ title = `${actorLabel} • Modification Utilisateur`;
224
+ IconComponent = UserCog;
225
+ if (log.targetUser?.name) {
226
+ subtitle = log.targetUser.name;
346
227
  }
347
- if (before.description !== after.description) {
348
- details.push(
349
- <li key="description" className="text-xs text-gray-600">
350
- <span className="font-medium">Description :</span>{' '}
351
- <span className="text-red-500 line-through">
352
- {before.description || 'vide'}
353
- </span>{' '}
354
- <span className="text-green-600">→ {after.description || 'vide'}</span>
355
- </li>,
356
- );
228
+ const changes = log.metadata?.changes as
229
+ | Record<string, { old: unknown; new: unknown }>
230
+ | undefined;
231
+ if (changes) {
232
+ const fieldLabels: Record<string, string> = {
233
+ name: 'Nom',
234
+ email: 'Email',
235
+ customRoleId: 'Profil',
236
+ profil: 'Profil',
237
+ active: 'Statut',
238
+ phone: 'Téléphone',
239
+ companyId: 'Société',
240
+ folderView: 'Vue des dossiers',
241
+ viewUnassignedContacts: 'Voir contacts non attribués',
242
+ };
243
+ details = Object.entries(changes).map(([field, value]) => {
244
+ const label = fieldLabels[field] || field;
245
+ const oldValue =
246
+ value.old === null || value.old === undefined || value.old === ''
247
+ ? 'vide'
248
+ : String(value.old);
249
+ const newValue =
250
+ value.new === null || value.new === undefined || value.new === ''
251
+ ? 'vide'
252
+ : String(value.new);
253
+ return (
254
+ <li key={field} className="text-xs text-gray-600">
255
+ <span className="font-medium">{label} :</span>{' '}
256
+ <span className="text-red-500 line-through">{oldValue}</span>{' '}
257
+ <span className="text-green-600">→ {newValue}</span>
258
+ </li>
259
+ );
260
+ });
357
261
  }
358
- const beforePerms = (before.permissions || []).length;
359
- const afterPerms = (after.permissions || []).length;
360
- if (beforePerms !== afterPerms) {
361
- details.push(
262
+ } else if (log.action === 'ROLE_CREATED') {
263
+ title = `${actorLabel} Ajout Profil`;
264
+ IconComponent = ShieldPlus;
265
+ const metadata = log.metadata as {
266
+ name?: string;
267
+ description?: string;
268
+ permissions?: string[];
269
+ };
270
+ if (metadata?.name) {
271
+ subtitle = metadata.name;
272
+ }
273
+ details = [
274
+ metadata?.description && (
275
+ <li key="description" className="text-xs text-gray-600">
276
+ <span className="font-medium">Description :</span> {metadata.description}
277
+ </li>
278
+ ),
279
+ metadata?.permissions && (
362
280
  <li key="permissions" className="text-xs text-gray-600">
363
281
  <span className="font-medium">Permissions :</span>{' '}
364
- <span className="text-red-500 line-through">{beforePerms}</span>{' '}
365
- <span className="text-green-600">→ {afterPerms}</span>
282
+ {metadata.permissions.length} permission
283
+ {metadata.permissions.length > 1 ? 's' : ''}
284
+ </li>
285
+ ),
286
+ ].filter(Boolean) as React.ReactElement[];
287
+ } else if (log.action === 'ROLE_UPDATED') {
288
+ title = `${actorLabel} • Modification Profil`;
289
+ IconComponent = ShieldCheck;
290
+ const metadata = log.metadata as {
291
+ before?: { name?: string; description?: string; permissions?: string[] };
292
+ after?: { name?: string; description?: string; permissions?: string[] };
293
+ };
294
+ if (metadata?.after?.name) {
295
+ subtitle = metadata.after.name;
296
+ }
297
+ if (metadata?.before && metadata?.after) {
298
+ const before = metadata.before;
299
+ const after = metadata.after;
300
+ if (before.name !== after.name) {
301
+ details.push(
302
+ <li key="name" className="text-xs text-gray-600">
303
+ <span className="font-medium">Nom :</span>{' '}
304
+ <span className="text-red-500 line-through">
305
+ {before.name || 'vide'}
306
+ </span>{' '}
307
+ <span className="text-green-600">→ {after.name || 'vide'}</span>
308
+ </li>,
309
+ );
310
+ }
311
+ if (before.description !== after.description) {
312
+ details.push(
313
+ <li key="description" className="text-xs text-gray-600">
314
+ <span className="font-medium">Description :</span>{' '}
315
+ <span className="text-red-500 line-through">
316
+ {before.description || 'vide'}
317
+ </span>{' '}
318
+ <span className="text-green-600">→ {after.description || 'vide'}</span>
319
+ </li>,
320
+ );
321
+ }
322
+ const beforePerms = (before.permissions || []).length;
323
+ const afterPerms = (after.permissions || []).length;
324
+ if (beforePerms !== afterPerms) {
325
+ details.push(
326
+ <li key="permissions" className="text-xs text-gray-600">
327
+ <span className="font-medium">Permissions :</span>{' '}
328
+ <span className="text-red-500 line-through">{beforePerms}</span>{' '}
329
+ <span className="text-green-600">→ {afterPerms}</span>
330
+ </li>,
331
+ );
332
+ }
333
+ }
334
+ } else if (log.action === 'ROLE_DELETED') {
335
+ title = `${actorLabel} • Suppression Profil`;
336
+ IconComponent = ShieldX;
337
+ const metadata = log.metadata as { name?: string; description?: string };
338
+ if (metadata?.name) {
339
+ subtitle = metadata.name;
340
+ }
341
+ if (metadata?.description) {
342
+ details.push(
343
+ <li key="description" className="text-xs text-gray-600">
344
+ <span className="font-medium">Description :</span> {metadata.description}
366
345
  </li>,
367
346
  );
368
347
  }
348
+ } else {
349
+ title = log.action;
369
350
  }
370
- } else if (log.action === 'ROLE_DELETED') {
371
- title = `${actorLabel} • Suppression Profil`;
372
- IconComponent = ShieldX;
373
- const metadata = log.metadata as { name?: string; description?: string };
374
- if (metadata?.name) {
375
- subtitle = metadata.name;
376
- }
377
- if (metadata?.description) {
378
- details.push(
379
- <li key="description" className="text-xs text-gray-600">
380
- <span className="font-medium">Description :</span> {metadata.description}
381
- </li>,
382
- );
383
- }
384
- } else {
385
- title = log.action;
386
- }
387
351
 
388
- // Couleur de l'icône selon le type d'action
389
- const iconColorClass =
390
- log.action === 'USER_CREATED' || log.action === 'ROLE_CREATED'
391
- ? 'text-green-600'
392
- : log.action === 'ROLE_DELETED'
393
- ? 'text-red-600'
394
- : 'text-indigo-600';
352
+ // Couleur de l'icône selon le type d'action
353
+ const iconColorClass =
354
+ log.action === 'USER_CREATED' || log.action === 'ROLE_CREATED'
355
+ ? 'text-green-600'
356
+ : log.action === 'ROLE_DELETED'
357
+ ? 'text-red-600'
358
+ : 'text-blue-600';
395
359
 
396
- return (
397
- <li key={log.id} className="py-5">
398
- <div className="flex items-start justify-between gap-6">
399
- <div className="flex flex-1 items-start gap-3">
400
- <div
401
- className={`mt-0.5 shrink-0 rounded-lg bg-gray-50 p-2 ${iconColorClass}`}
402
- >
403
- <IconComponent className="h-5 w-5" />
360
+ return (
361
+ <li key={log.id} className="py-5">
362
+ <div className="flex items-start justify-between gap-6">
363
+ <div className="flex flex-1 items-start gap-3">
364
+ <div
365
+ className={`mt-0.5 shrink-0 rounded-lg bg-gray-50 p-2 ${iconColorClass}`}
366
+ >
367
+ <IconComponent className="h-5 w-5" />
368
+ </div>
369
+ <div className="flex-1">
370
+ <div className="mb-1 text-sm font-semibold text-gray-900">{title}</div>
371
+ {subtitle && (
372
+ <div className="mb-1 text-sm font-medium text-gray-700">
373
+ {subtitle}
374
+ </div>
375
+ )}
376
+ {details.length > 0 && <ul className="mt-3 space-y-1.5">{details}</ul>}
377
+ </div>
404
378
  </div>
405
- <div className="flex-1">
406
- <div className="mb-1 text-sm font-semibold text-gray-900">{title}</div>
407
- {subtitle && (
408
- <div className="mb-1 text-sm font-medium text-gray-700">{subtitle}</div>
409
- )}
410
- {details.length > 0 && <ul className="mt-3 space-y-1.5">{details}</ul>}
379
+ <div className="shrink-0 text-xs font-medium text-gray-400">
380
+ {dateLabel}
411
381
  </div>
412
382
  </div>
413
- <div className="shrink-0 text-xs font-medium text-gray-400">{dateLabel}</div>
414
- </div>
415
- </li>
416
- );
417
- })}
418
- </ul>
419
- )}
383
+ </li>
384
+ );
385
+ })}
386
+ </ul>
387
+ )}
388
+ </div>
420
389
  </div>
421
390
  </div>
422
- </div>
391
+ </ProtectedPage>
423
392
  );
424
393
  }