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,181 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { auth } from '@/lib/auth';
3
- import { headers } from 'next/headers';
4
- import { prisma } from '@/lib/prisma';
5
- import { DEFAULT_WIDGETS } from '@/lib/default-widgets';
6
- import { checkPermission } from '@/lib/check-permission';
7
-
8
- // GET - Récupérer les widgets de l'utilisateur
9
- export async function GET() {
10
- try {
11
- const session = await auth.api.getSession({
12
- headers: await headers(),
13
- });
14
-
15
- if (!session) {
16
- return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
17
- }
18
-
19
- // Vérifier la permission d'accès au tableau de bord
20
- const canView = await checkPermission('dashboard.view');
21
- if (!canView) {
22
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
23
- }
24
-
25
- const widgets = await prisma.dashboardWidget.findMany({
26
- where: { userId: session.user.id },
27
- orderBy: [{ y: 'asc' }, { x: 'asc' }],
28
- });
29
-
30
- return NextResponse.json(widgets);
31
- } catch (error) {
32
- console.error('Erreur lors de la récupération des widgets:', error);
33
- return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
34
- }
35
- }
36
-
37
- // POST - Créer un widget (ou initialiser le layout par défaut)
38
- export async function POST(req: NextRequest) {
39
- try {
40
- const session = await auth.api.getSession({
41
- headers: await headers(),
42
- });
43
-
44
- if (!session) {
45
- return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
46
- }
47
-
48
- // Vérifier la permission de gestion des widgets
49
- const canManage = await checkPermission('dashboard.widgets.manage');
50
- if (!canManage) {
51
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
52
- }
53
-
54
- const body = await req.json();
55
-
56
- // Si c'est une initialisation par défaut
57
- if (body.initDefault) {
58
- const existing = await prisma.dashboardWidget.count({
59
- where: { userId: session.user.id },
60
- });
61
-
62
- if (existing > 0) {
63
- const widgets = await prisma.dashboardWidget.findMany({
64
- where: { userId: session.user.id },
65
- orderBy: [{ y: 'asc' }, { x: 'asc' }],
66
- });
67
- return NextResponse.json(widgets);
68
- }
69
-
70
- const created = await prisma.$transaction(
71
- DEFAULT_WIDGETS.map((dw) =>
72
- prisma.dashboardWidget.create({
73
- data: {
74
- userId: session.user.id,
75
- type: dw.type,
76
- x: dw.x,
77
- y: dw.y,
78
- w: dw.w,
79
- h: dw.h,
80
- },
81
- }),
82
- ),
83
- );
84
-
85
- return NextResponse.json(created, { status: 201 });
86
- }
87
-
88
- // Création d'un widget unique
89
- const {
90
- type,
91
- x,
92
- y,
93
- w: width,
94
- h: height,
95
- } = body as {
96
- type?: string;
97
- x?: number;
98
- y?: number;
99
- w?: number;
100
- h?: number;
101
- };
102
-
103
- if (!type) {
104
- return NextResponse.json({ error: 'Le type de widget est requis' }, { status: 400 });
105
- }
106
-
107
- // Calculer la position Y max pour placer le nouveau widget en bas
108
- const lastWidget = await prisma.dashboardWidget.findFirst({
109
- where: { userId: session.user.id },
110
- orderBy: { y: 'desc' },
111
- });
112
-
113
- const newY = lastWidget ? lastWidget.y + lastWidget.h : 0;
114
-
115
- const widget = await prisma.dashboardWidget.create({
116
- data: {
117
- userId: session.user.id,
118
- type,
119
- x: x ?? 0,
120
- y: y ?? newY,
121
- w: width ?? 6,
122
- h: height ?? 4,
123
- },
124
- });
125
-
126
- return NextResponse.json(widget, { status: 201 });
127
- } catch (error) {
128
- console.error('Erreur lors de la création du widget:', error);
129
- return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
130
- }
131
- }
132
-
133
- // PUT - Mettre à jour les positions des widgets (batch)
134
- export async function PUT(req: NextRequest) {
135
- try {
136
- const session = await auth.api.getSession({
137
- headers: await headers(),
138
- });
139
-
140
- if (!session) {
141
- return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
142
- }
143
-
144
- // Vérifier la permission de gestion des widgets
145
- const canManage = await checkPermission('dashboard.widgets.manage');
146
- if (!canManage) {
147
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
148
- }
149
-
150
- const body = await req.json();
151
- const { widgets } = body as {
152
- widgets: Array<{ id: string; x: number; y: number; w: number; h: number }>;
153
- };
154
-
155
- if (!widgets || !Array.isArray(widgets)) {
156
- return NextResponse.json({ error: 'Format invalide' }, { status: 400 });
157
- }
158
-
159
- await prisma.$transaction(
160
- widgets.map((w) =>
161
- prisma.dashboardWidget.updateMany({
162
- where: {
163
- id: w.id,
164
- userId: session.user.id,
165
- },
166
- data: {
167
- x: w.x,
168
- y: w.y,
169
- w: w.w,
170
- h: w.h,
171
- },
172
- }),
173
- ),
174
- );
175
-
176
- return NextResponse.json({ success: true });
177
- } catch (error) {
178
- console.error('Erreur lors de la mise à jour des widgets:', error);
179
- return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
180
- }
181
- }
@@ -1,161 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { X, Plus, BarChart3, ListTodo, TrendingUp } from 'lucide-react';
5
- import { cn } from '@/lib/utils';
6
- import { WIDGET_REGISTRY, type WidgetDefinition } from '@/lib/widget-registry';
7
-
8
- interface AddWidgetDialogProps {
9
- readonly isOpen: boolean;
10
- readonly onClose: () => void;
11
- readonly onAdd: (type: string, w: number, h: number) => void;
12
- readonly existingTypes: string[];
13
- }
14
-
15
- const CATEGORY_LABELS: Record<string, { label: string; icon: typeof BarChart3 }> = {
16
- stat: { label: 'Statistiques', icon: TrendingUp },
17
- chart: { label: 'Graphiques', icon: BarChart3 },
18
- list: { label: 'Listes', icon: ListTodo },
19
- };
20
-
21
- export function AddWidgetDialog({ isOpen, onClose, onAdd, existingTypes }: AddWidgetDialogProps) {
22
- const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
23
-
24
- if (!isOpen) return null;
25
-
26
- const categories = ['stat', 'chart', 'list'] as const;
27
-
28
- const filteredWidgets = selectedCategory
29
- ? WIDGET_REGISTRY.filter((w) => w.category === selectedCategory)
30
- : WIDGET_REGISTRY;
31
-
32
- const handleAdd = (widget: WidgetDefinition) => {
33
- onAdd(widget.type, widget.defaultW, widget.defaultH);
34
- };
35
-
36
- return (
37
- <div className="fixed inset-0 z-50 flex items-center justify-center">
38
- {/* Backdrop */}
39
- <div
40
- className="absolute inset-0 cursor-pointer bg-black/40 backdrop-blur-sm"
41
- role="button"
42
- tabIndex={0}
43
- onClick={onClose}
44
- onKeyDown={(e) => {
45
- if (e.key === 'Escape') onClose();
46
- }}
47
- />
48
-
49
- {/* Dialog */}
50
- <div className="relative z-10 w-full max-w-2xl rounded-2xl bg-white shadow-2xl">
51
- {/* Header */}
52
- <div className="flex items-center justify-between border-b border-gray-100 px-6 py-4">
53
- <div>
54
- <h2 className="text-lg font-semibold text-gray-900">Ajouter un Widget</h2>
55
- <p className="mt-0.5 text-sm text-gray-500">
56
- Choisissez un widget à ajouter au tableau de bord
57
- </p>
58
- </div>
59
- <button
60
- onClick={onClose}
61
- className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
62
- >
63
- <X className="h-4 w-4" />
64
- </button>
65
- </div>
66
-
67
- {/* Filtres catégories */}
68
- <div className="flex gap-2 border-b border-gray-100 px-6 py-3">
69
- <button
70
- onClick={() => setSelectedCategory(null)}
71
- className={cn(
72
- 'cursor-pointer rounded-full px-3.5 py-1.5 text-xs font-medium transition-colors',
73
- selectedCategory === null
74
- ? 'dash-pill-active'
75
- : 'bg-gray-100 text-gray-600 hover:bg-gray-200',
76
- )}
77
- >
78
- Tous
79
- </button>
80
- {categories.map((cat) => {
81
- const { label, icon: Icon } = CATEGORY_LABELS[cat];
82
- return (
83
- <button
84
- key={cat}
85
- onClick={() => setSelectedCategory(cat)}
86
- className={cn(
87
- 'flex cursor-pointer items-center gap-1.5 rounded-full px-3.5 py-1.5 text-xs font-medium transition-colors',
88
- selectedCategory === cat
89
- ? 'dash-pill-active'
90
- : 'bg-gray-100 text-gray-600 hover:bg-gray-200',
91
- )}
92
- >
93
- <Icon className="h-3 w-3" />
94
- {label}
95
- </button>
96
- );
97
- })}
98
- </div>
99
-
100
- {/* Grille de widgets */}
101
- <div className="max-h-[400px] overflow-y-auto p-6">
102
- <div className="grid grid-cols-2 gap-3">
103
- {filteredWidgets.map((widget) => {
104
- const isAlreadyAdded = existingTypes.includes(widget.type);
105
- const Icon = widget.icon;
106
-
107
- return (
108
- <button
109
- key={widget.type}
110
- onClick={() => !isAlreadyAdded && handleAdd(widget)}
111
- disabled={isAlreadyAdded}
112
- className={cn(
113
- 'group flex items-start gap-3 rounded-xl border p-4 text-left transition-all duration-150',
114
- isAlreadyAdded
115
- ? 'cursor-not-allowed border-gray-100 bg-gray-50 opacity-50'
116
- : 'border-gray-100 bg-white dash-hover-border dash-hover-bg hover:shadow-sm',
117
- )}
118
- >
119
- <div
120
- className={cn(
121
- 'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',
122
- isAlreadyAdded ? 'bg-gray-100' : 'dash-icon-box dash-icon-box-gh',
123
- )}
124
- >
125
- <Icon
126
- className={cn(
127
- 'h-4 w-4',
128
- isAlreadyAdded ? 'text-gray-400' : 'dash-icon-color',
129
- )}
130
- />
131
- </div>
132
- <div className="min-w-0 flex-1">
133
- <div className="flex items-center justify-between">
134
- <p className="text-sm font-medium text-gray-900">{widget.label}</p>
135
- {isAlreadyAdded ? (
136
- <span className="text-[10px] font-medium text-gray-400">Déjà ajouté</span>
137
- ) : (
138
- <Plus className="h-4 w-4 text-gray-300 transition-colors dash-hover-text-gh" />
139
- )}
140
- </div>
141
- <p className="mt-0.5 text-xs text-gray-500">{widget.description}</p>
142
- <p className="mt-1 text-[10px] text-gray-400">
143
- Taille : {widget.defaultW}x{widget.defaultH}
144
- </p>
145
- </div>
146
- </button>
147
- );
148
- })}
149
- </div>
150
- </div>
151
-
152
- {/* Footer */}
153
- <div className="border-t border-gray-100 px-6 py-3">
154
- <p className="text-center text-xs text-gray-400">
155
- Les widgets peuvent être déplacés et redimensionnés après ajout
156
- </p>
157
- </div>
158
- </div>
159
- </div>
160
- );
161
- }
@@ -1,65 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useRef, useEffect } from 'react';
4
- import { Palette, Check } from 'lucide-react';
5
- import { useDashboardTheme } from '@/contexts/dashboard-theme-context';
6
-
7
- export function DashboardColorPicker() {
8
- const { theme, setThemeKey, themes } = useDashboardTheme();
9
- const [isOpen, setIsOpen] = useState(false);
10
- const ref = useRef<HTMLDivElement>(null);
11
-
12
- // Fermer le dropdown au clic extérieur
13
- useEffect(() => {
14
- function handleClickOutside(event: MouseEvent) {
15
- if (ref.current && !ref.current.contains(event.target as Node)) {
16
- setIsOpen(false);
17
- }
18
- }
19
- if (isOpen) {
20
- document.addEventListener('mousedown', handleClickOutside);
21
- return () => document.removeEventListener('mousedown', handleClickOutside);
22
- }
23
- }, [isOpen]);
24
-
25
- return (
26
- <div ref={ref} className="relative">
27
- <button
28
- onClick={() => setIsOpen(!isOpen)}
29
- className="inline-flex h-10 w-10 cursor-pointer items-center justify-center rounded-xl border border-gray-200 bg-white shadow-sm transition-all duration-150 hover:bg-gray-50 hover:shadow-md active:scale-[0.98]"
30
- title="Changer la couleur du thème"
31
- >
32
- <Palette className="h-4 w-4 text-gray-600" />
33
- </button>
34
-
35
- {isOpen && (
36
- <div className="absolute right-0 z-50 mt-2 w-56 rounded-xl border border-gray-100 bg-white p-3 shadow-xl">
37
- <p className="mb-2.5 text-[11px] font-medium tracking-wider text-gray-400 uppercase">
38
- Couleur d&apos;accent
39
- </p>
40
- <div className="grid grid-cols-4 gap-2">
41
- {themes.map((t) => (
42
- <button
43
- key={t.key}
44
- onClick={() => {
45
- setThemeKey(t.key);
46
- setIsOpen(false);
47
- }}
48
- className="group relative flex h-10 w-10 cursor-pointer items-center justify-center rounded-xl transition-all duration-150 hover:scale-110"
49
- style={{ backgroundColor: t.hex[500] }}
50
- title={t.label}
51
- >
52
- {theme.key === t.key && (
53
- <Check className="h-4 w-4 text-white drop-shadow-sm" />
54
- )}
55
- <span className="absolute -bottom-5 left-1/2 -translate-x-1/2 whitespace-nowrap text-[10px] text-gray-500 opacity-0 transition-opacity group-hover:opacity-100">
56
- {t.label}
57
- </span>
58
- </button>
59
- ))}
60
- </div>
61
- </div>
62
- )}
63
- </div>
64
- );
65
- }
@@ -1,69 +0,0 @@
1
- 'use client';
2
-
3
- import {
4
- Area,
5
- AreaChart,
6
- ResponsiveContainer,
7
- Tooltip,
8
- XAxis,
9
- YAxis,
10
- CartesianGrid,
11
- } from 'recharts';
12
- import { useDashboardTheme } from '@/contexts/dashboard-theme-context';
13
-
14
- interface ContactsChartProps {
15
- readonly data: Array<{ month: string; count: number }>;
16
- }
17
-
18
- export function ContactsChart({ data }: Readonly<ContactsChartProps>) {
19
- const { theme } = useDashboardTheme();
20
-
21
- return (
22
- <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
23
- <div className="mb-4">
24
- <h3 className="text-base font-semibold text-gray-900">Évolution des Contacts</h3>
25
- <p className="mt-0.5 text-xs text-gray-400">Contacts créés par mois</p>
26
- </div>
27
- <div className="min-h-0 flex-1">
28
- <ResponsiveContainer width="100%" height="100%">
29
- <AreaChart data={data} margin={{ top: 5, right: 5, left: -20, bottom: 0 }}>
30
- <defs>
31
- <linearGradient id="colorContactsAccent" x1="0" y1="0" x2="0" y2="1">
32
- <stop offset="0%" stopColor={theme.hex[500]} stopOpacity={0.3} />
33
- <stop offset="95%" stopColor={theme.hex[500]} stopOpacity={0.02} />
34
- </linearGradient>
35
- </defs>
36
- <CartesianGrid strokeDasharray="3 3" stroke="#f3f4f6" vertical={false} />
37
- <XAxis
38
- dataKey="month"
39
- stroke="#d1d5db"
40
- fontSize={11}
41
- tickLine={false}
42
- axisLine={false}
43
- dy={8}
44
- />
45
- <YAxis stroke="#d1d5db" fontSize={11} tickLine={false} axisLine={false} width={40} />
46
- <Tooltip
47
- contentStyle={{
48
- backgroundColor: '#fff',
49
- border: '1px solid #f3f4f6',
50
- borderRadius: '12px',
51
- boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
52
- fontSize: '13px',
53
- }}
54
- labelStyle={{ color: '#374151', fontWeight: 600 }}
55
- />
56
- <Area
57
- type="monotone"
58
- dataKey="count"
59
- stroke={theme.hex[500]}
60
- strokeWidth={2.5}
61
- fill="url(#colorContactsAccent)"
62
- name="Contacts"
63
- />
64
- </AreaChart>
65
- </ResponsiveContainer>
66
- </div>
67
- </div>
68
- );
69
- }
@@ -1,121 +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
-
14
- interface InteractionsByTypeChartProps {
15
- readonly data: Array<{ type: string; count: number }>;
16
- }
17
-
18
- const TYPE_LABELS: Record<string, string> = {
19
- CALL: 'Appels',
20
- SMS: 'SMS',
21
- EMAIL: 'Emails',
22
- MEETING: 'Réunions',
23
- NOTE: 'Notes',
24
- TASK: 'Tâches',
25
- STATUS_CHANGE: 'Changements',
26
- APPOINTMENT_CREATED: 'RDV créés',
27
- APPOINTMENT_DELETED: 'RDV supprimés',
28
- APPOINTMENT_CHANGED: 'RDV modifiés',
29
- ASSIGNMENT_CHANGE: 'Assignations',
30
- CONTACT_UPDATE: 'Mises à jour',
31
- FILE_UPLOADED: 'Fichiers',
32
- FILE_REPLACED: 'Fichiers remplacés',
33
- FILE_DELETED: 'Fichiers supprimés',
34
- };
35
-
36
- const TYPE_COLORS: Record<string, string> = {
37
- CALL: '#3b82f6',
38
- SMS: '#10b981',
39
- EMAIL: '#f97316',
40
- MEETING: '#8b5cf6',
41
- NOTE: '#6b7280',
42
- TASK: '#f59e0b',
43
- STATUS_CHANGE: '#ef4444',
44
- APPOINTMENT_CREATED: '#14b8a6',
45
- APPOINTMENT_DELETED: '#ef4444',
46
- APPOINTMENT_CHANGED: '#f97316',
47
- ASSIGNMENT_CHANGE: '#ec4899',
48
- CONTACT_UPDATE: '#06b6d4',
49
- FILE_UPLOADED: '#84cc16',
50
- FILE_REPLACED: '#a855f7',
51
- FILE_DELETED: '#dc2626',
52
- };
53
-
54
- export function InteractionsByTypeChart({ data }: Readonly<InteractionsByTypeChartProps>) {
55
- const chartData = data
56
- .filter((d) => d.count > 0)
57
- .map((d) => ({
58
- ...d,
59
- label: TYPE_LABELS[d.type] || d.type,
60
- fill: TYPE_COLORS[d.type] || '#f97316',
61
- }))
62
- .sort((a, b) => b.count - a.count);
63
-
64
- return (
65
- <div className="flex h-full flex-col rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
66
- <div className="mb-4">
67
- <h3 className="text-base font-semibold text-gray-900">Interactions par Type</h3>
68
- <p className="mt-0.5 text-xs text-gray-400">Répartition des interactions ce mois</p>
69
- </div>
70
-
71
- {chartData.length === 0 ? (
72
- <div className="flex flex-1 items-center justify-center">
73
- <p className="text-sm text-gray-400">Aucune interaction ce mois</p>
74
- </div>
75
- ) : (
76
- <div className="min-h-0 flex-1">
77
- <ResponsiveContainer width="100%" height="100%">
78
- <BarChart
79
- data={chartData}
80
- layout="vertical"
81
- margin={{ top: 0, right: 20, left: 0, bottom: 0 }}
82
- >
83
- <CartesianGrid strokeDasharray="3 3" stroke="#f3f4f6" horizontal={false} />
84
- <XAxis
85
- type="number"
86
- stroke="#d1d5db"
87
- fontSize={11}
88
- tickLine={false}
89
- axisLine={false}
90
- />
91
- <YAxis
92
- dataKey="label"
93
- type="category"
94
- stroke="#d1d5db"
95
- fontSize={11}
96
- tickLine={false}
97
- axisLine={false}
98
- width={80}
99
- />
100
- <Tooltip
101
- contentStyle={{
102
- backgroundColor: '#fff',
103
- border: '1px solid #f3f4f6',
104
- borderRadius: '12px',
105
- boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
106
- fontSize: '13px',
107
- }}
108
- formatter={(value: number) => [value, 'Interactions']}
109
- />
110
- <Bar dataKey="count" radius={[0, 6, 6, 0]} barSize={16}>
111
- {chartData.map((entry) => (
112
- <Cell key={`type-${entry.label}`} fill={entry.fill} />
113
- ))}
114
- </Bar>
115
- </BarChart>
116
- </ResponsiveContainer>
117
- </div>
118
- )}
119
- </div>
120
- );
121
- }