create-crm-tmp 1.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 (187) hide show
  1. package/bin/create-crm-tmp.js +93 -0
  2. package/package.json +25 -0
  3. package/template/.prettierignore +33 -0
  4. package/template/.prettierrc.json +25 -0
  5. package/template/README.md +173 -0
  6. package/template/eslint.config.mjs +18 -0
  7. package/template/exemple-contacts.csv +11 -0
  8. package/template/next.config.ts +8 -0
  9. package/template/package.json +64 -0
  10. package/template/postcss.config.mjs +7 -0
  11. package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
  12. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
  13. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
  14. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
  15. package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
  16. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
  17. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
  18. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
  19. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
  20. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
  21. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
  22. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
  23. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
  24. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
  25. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
  26. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
  27. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
  28. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
  29. package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
  30. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
  31. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
  32. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
  33. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
  34. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
  35. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
  36. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
  37. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
  38. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
  39. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
  40. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
  41. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
  42. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
  43. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
  44. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
  45. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
  46. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
  47. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
  48. package/template/prisma/migrations/migration_lock.toml +3 -0
  49. package/template/prisma/schema.prisma +582 -0
  50. package/template/prisma.config.ts +14 -0
  51. package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
  52. package/template/src/app/(auth)/layout.tsx +3 -0
  53. package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
  54. package/template/src/app/(auth)/reset-password/page.tsx +146 -0
  55. package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
  56. package/template/src/app/(auth)/signin/page.tsx +166 -0
  57. package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
  58. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
  59. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
  60. package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
  61. package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
  62. package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
  63. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
  64. package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
  65. package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
  66. package/template/src/app/(dashboard)/layout.tsx +30 -0
  67. package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
  68. package/template/src/app/(dashboard)/templates/page.tsx +567 -0
  69. package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
  70. package/template/src/app/(dashboard)/users/page.tsx +457 -0
  71. package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
  72. package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
  73. package/template/src/app/api/audit-logs/route.ts +57 -0
  74. package/template/src/app/api/auth/[...all]/route.ts +4 -0
  75. package/template/src/app/api/auth/check-active/route.ts +31 -0
  76. package/template/src/app/api/auth/google/callback/route.ts +94 -0
  77. package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
  78. package/template/src/app/api/auth/google/route.ts +34 -0
  79. package/template/src/app/api/auth/google/status/route.ts +32 -0
  80. package/template/src/app/api/closing-reasons/route.ts +27 -0
  81. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
  82. package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
  83. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
  84. package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
  85. package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
  86. package/template/src/app/api/contacts/[id]/route.ts +322 -0
  87. package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
  88. package/template/src/app/api/contacts/export/route.ts +270 -0
  89. package/template/src/app/api/contacts/import/route.ts +381 -0
  90. package/template/src/app/api/contacts/route.ts +283 -0
  91. package/template/src/app/api/dashboard/stats/route.ts +299 -0
  92. package/template/src/app/api/email/track/[id]/route.ts +68 -0
  93. package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
  94. package/template/src/app/api/invite/complete/route.ts +88 -0
  95. package/template/src/app/api/invite/validate/route.ts +55 -0
  96. package/template/src/app/api/reminders/route.ts +95 -0
  97. package/template/src/app/api/reset-password/complete/route.ts +73 -0
  98. package/template/src/app/api/reset-password/request/route.ts +84 -0
  99. package/template/src/app/api/reset-password/validate/route.ts +49 -0
  100. package/template/src/app/api/reset-password/verify/route.ts +74 -0
  101. package/template/src/app/api/roles/[id]/route.ts +183 -0
  102. package/template/src/app/api/roles/route.ts +140 -0
  103. package/template/src/app/api/send/route.ts +282 -0
  104. package/template/src/app/api/settings/change-password/route.ts +95 -0
  105. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
  106. package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
  107. package/template/src/app/api/settings/company/route.ts +121 -0
  108. package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
  109. package/template/src/app/api/settings/google-ads/route.ts +122 -0
  110. package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
  111. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
  112. package/template/src/app/api/settings/google-sheet/route.ts +254 -0
  113. package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
  114. package/template/src/app/api/settings/meta-leads/route.ts +132 -0
  115. package/template/src/app/api/settings/profile/route.ts +42 -0
  116. package/template/src/app/api/settings/smtp/route.ts +130 -0
  117. package/template/src/app/api/settings/smtp/test/route.ts +121 -0
  118. package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
  119. package/template/src/app/api/settings/statuses/route.ts +83 -0
  120. package/template/src/app/api/statuses/route.ts +25 -0
  121. package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
  122. package/template/src/app/api/tasks/[id]/route.ts +728 -0
  123. package/template/src/app/api/tasks/meet/route.ts +240 -0
  124. package/template/src/app/api/tasks/route.ts +417 -0
  125. package/template/src/app/api/templates/[id]/route.ts +140 -0
  126. package/template/src/app/api/templates/route.ts +91 -0
  127. package/template/src/app/api/users/[id]/route.ts +168 -0
  128. package/template/src/app/api/users/list/route.ts +45 -0
  129. package/template/src/app/api/users/me/route.ts +48 -0
  130. package/template/src/app/api/users/route.ts +250 -0
  131. package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
  132. package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
  133. package/template/src/app/api/workflows/[id]/route.ts +192 -0
  134. package/template/src/app/api/workflows/process/route.ts +293 -0
  135. package/template/src/app/api/workflows/route.ts +124 -0
  136. package/template/src/app/favicon.ico +0 -0
  137. package/template/src/app/globals.css +1416 -0
  138. package/template/src/app/layout.tsx +31 -0
  139. package/template/src/app/page.tsx +32 -0
  140. package/template/src/components/dashboard/activity-chart.tsx +67 -0
  141. package/template/src/components/dashboard/contacts-chart.tsx +63 -0
  142. package/template/src/components/dashboard/recent-activity.tsx +164 -0
  143. package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
  144. package/template/src/components/dashboard/stat-card.tsx +61 -0
  145. package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
  146. package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
  147. package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
  148. package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
  149. package/template/src/components/editor.tsx +856 -0
  150. package/template/src/components/email-template.tsx +35 -0
  151. package/template/src/components/header.tsx +320 -0
  152. package/template/src/components/invitation-email-template.tsx +79 -0
  153. package/template/src/components/meet-cancellation-email-template.tsx +120 -0
  154. package/template/src/components/meet-confirmation-email-template.tsx +156 -0
  155. package/template/src/components/meet-update-email-template.tsx +209 -0
  156. package/template/src/components/page-header.tsx +61 -0
  157. package/template/src/components/reset-password-email-template.tsx +79 -0
  158. package/template/src/components/sidebar.tsx +294 -0
  159. package/template/src/components/skeleton.tsx +380 -0
  160. package/template/src/components/ui/commands.tsx +396 -0
  161. package/template/src/components/ui/components.tsx +150 -0
  162. package/template/src/components/ui/theme.tsx +5 -0
  163. package/template/src/components/view-as-banner.tsx +45 -0
  164. package/template/src/components/view-as-modal.tsx +186 -0
  165. package/template/src/contexts/mobile-menu-context.tsx +31 -0
  166. package/template/src/contexts/sidebar-context.tsx +107 -0
  167. package/template/src/contexts/task-reminder-context.tsx +239 -0
  168. package/template/src/contexts/view-as-context.tsx +84 -0
  169. package/template/src/hooks/use-user-role.ts +82 -0
  170. package/template/src/lib/audit-log.ts +45 -0
  171. package/template/src/lib/auth-client.ts +16 -0
  172. package/template/src/lib/auth.ts +35 -0
  173. package/template/src/lib/check-permission.ts +193 -0
  174. package/template/src/lib/contact-duplicate.ts +112 -0
  175. package/template/src/lib/contact-interactions.ts +371 -0
  176. package/template/src/lib/encryption.ts +99 -0
  177. package/template/src/lib/google-calendar.ts +300 -0
  178. package/template/src/lib/google-drive.ts +372 -0
  179. package/template/src/lib/permissions.ts +412 -0
  180. package/template/src/lib/prisma.ts +32 -0
  181. package/template/src/lib/roles.ts +120 -0
  182. package/template/src/lib/template-variables.ts +76 -0
  183. package/template/src/lib/utils.ts +46 -0
  184. package/template/src/lib/workflow-executor.ts +482 -0
  185. package/template/src/proxy.ts +91 -0
  186. package/template/tsconfig.json +34 -0
  187. package/template/vercel.json +8 -0
@@ -0,0 +1,4070 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { useSession } from '@/lib/auth-client';
6
+ import { useUserRole } from '@/hooks/use-user-role';
7
+ import { PageHeader } from '@/components/page-header';
8
+ import { cn } from '@/lib/utils';
9
+ import Link from 'next/link';
10
+ import { Editor, type DefaultTemplateRef } from '@/components/editor';
11
+ import {
12
+ Eye,
13
+ EyeOff,
14
+ Plus,
15
+ Trash2,
16
+ ArrowRight,
17
+ Settings,
18
+ Globe,
19
+ Grid3x3,
20
+ Monitor,
21
+ DollarSign,
22
+ Wrench,
23
+ User,
24
+ Lock,
25
+ Bell,
26
+ Plug,
27
+ RefreshCw,
28
+ } from 'lucide-react';
29
+
30
+ // Fonction utilitaire pour convertir un index en lettre de colonne (A, B, C, ...)
31
+ function indexToColumn(index: number): string {
32
+ let col = '';
33
+ let n = index + 1;
34
+ while (n > 0) {
35
+ const remainder = (n - 1) % 26;
36
+ col = String.fromCharCode(65 + remainder) + col;
37
+ n = Math.floor((n - 1) / 26);
38
+ }
39
+ return col;
40
+ }
41
+
42
+ export default function SettingsPage() {
43
+ const router = useRouter();
44
+ const { data: session } = useSession();
45
+ const { isAdmin } = useUserRole();
46
+ const [showPasswordForm, setShowPasswordForm] = useState(false);
47
+ const [passwordData, setPasswordData] = useState({
48
+ currentPassword: '',
49
+ newPassword: '',
50
+ confirmPassword: '',
51
+ });
52
+ const [passwordError, setPasswordError] = useState('');
53
+ const [passwordSuccess, setPasswordSuccess] = useState('');
54
+ const [passwordLoading, setPasswordLoading] = useState(false);
55
+
56
+ // État pour la modification du nom
57
+ const [showNameForm, setShowNameForm] = useState(false);
58
+ const [nameValue, setNameValue] = useState(session?.user?.name || '');
59
+ const [nameError, setNameError] = useState('');
60
+ const [nameSuccess, setNameSuccess] = useState('');
61
+ const [nameLoading, setNameLoading] = useState(false);
62
+
63
+ // État pour les informations de l'entreprise
64
+ const [companyData, setCompanyData] = useState({
65
+ name: '',
66
+ address: '',
67
+ city: '',
68
+ postalCode: '',
69
+ country: '',
70
+ phone: '',
71
+ email: '',
72
+ website: '',
73
+ siret: '',
74
+ vatNumber: '',
75
+ logo: '',
76
+ });
77
+ const [companyLoading, setCompanyLoading] = useState(true);
78
+ const [companySaving, setCompanySaving] = useState(false);
79
+ const [companyError, setCompanyError] = useState('');
80
+ const [companySuccess, setCompanySuccess] = useState('');
81
+
82
+ // État pour la configuration SMTP
83
+ const [smtpData, setSmtpData] = useState({
84
+ host: '',
85
+ port: '587',
86
+ secure: false,
87
+ username: '',
88
+ password: '',
89
+ fromEmail: '',
90
+ fromName: '',
91
+ signature: '',
92
+ });
93
+ const [smtpLoading, setSmtpLoading] = useState(true);
94
+ const [smtpSaving, setSmtpSaving] = useState(false);
95
+ const [smtpError, setSmtpError] = useState('');
96
+ const [smtpSuccess, setSmtpSuccess] = useState('');
97
+ const [smtpTesting, setSmtpTesting] = useState(false);
98
+ const [showSmtpPassword, setShowSmtpPassword] = useState(false);
99
+ const [smtpTestResult, setSmtpTestResult] = useState<{
100
+ success: boolean;
101
+ message: string;
102
+ } | null>(null);
103
+ const [smtpConfigured, setSmtpConfigured] = useState(false);
104
+ const smtpSignatureEditorRef = useRef<DefaultTemplateRef | null>(null);
105
+
106
+ // État pour la gestion des statuts
107
+ interface Status {
108
+ id: string;
109
+ name: string;
110
+ color: string;
111
+ order: number;
112
+ }
113
+ const [statuses, setStatuses] = useState<Status[]>([]);
114
+ const [statusesLoading, setStatusesLoading] = useState(true);
115
+ const [showStatusForm, setShowStatusForm] = useState(false);
116
+ const [editingStatus, setEditingStatus] = useState<Status | null>(null);
117
+ const [statusFormData, setStatusFormData] = useState({
118
+ name: '',
119
+ color: '#3B82F6',
120
+ });
121
+ const [statusError, setStatusError] = useState('');
122
+ const [statusSuccess, setStatusSuccess] = useState('');
123
+ const [statusSaving, setStatusSaving] = useState(false);
124
+
125
+ // État pour Google Calendar
126
+ const [googleAccount, setGoogleAccount] = useState<{
127
+ email: string | null;
128
+ connected: boolean;
129
+ } | null>(null);
130
+ const [googleLoading, setGoogleLoading] = useState(true);
131
+ const [googleDisconnecting, setGoogleDisconnecting] = useState(false);
132
+
133
+ // État pour l'intégration Meta Lead Ads (admin uniquement)
134
+ const [metaLeadLoading, setMetaLeadLoading] = useState(true);
135
+ const [metaLeadSaving, setMetaLeadSaving] = useState(false);
136
+ const [metaLeadError, setMetaLeadError] = useState('');
137
+ const [metaLeadSuccess, setMetaLeadSuccess] = useState('');
138
+ const [metaLeadUsers, setMetaLeadUsers] = useState<{ id: string; name: string; email: string }[]>(
139
+ [],
140
+ );
141
+ const [metaLeadConfigs, setMetaLeadConfigs] = useState<
142
+ Array<{
143
+ id: string;
144
+ name: string;
145
+ active: boolean;
146
+ pageId: string;
147
+ verifyToken: string;
148
+ defaultStatusId: string | null;
149
+ defaultAssignedUserId: string | null;
150
+ }>
151
+ >([]);
152
+ const [showMetaLeadModal, setShowMetaLeadModal] = useState(false);
153
+ const [editingMetaLeadConfig, setEditingMetaLeadConfig] = useState<string | null>(null);
154
+ const [metaLeadFormData, setMetaLeadFormData] = useState<{
155
+ name: string;
156
+ active: boolean;
157
+ pageId: string;
158
+ accessToken: string;
159
+ verifyToken: string;
160
+ defaultStatusId: string | null;
161
+ defaultAssignedUserId: string | null;
162
+ }>({
163
+ name: '',
164
+ active: true,
165
+ pageId: '',
166
+ accessToken: '',
167
+ verifyToken: '',
168
+ defaultStatusId: null,
169
+ defaultAssignedUserId: null,
170
+ });
171
+
172
+ // État pour l'intégration Google Ads Lead Forms (admin uniquement)
173
+ const [googleAdsLoading, setGoogleAdsLoading] = useState(true);
174
+ const [googleAdsSaving, setGoogleAdsSaving] = useState(false);
175
+ const [googleAdsError, setGoogleAdsError] = useState('');
176
+ const [googleAdsSuccess, setGoogleAdsSuccess] = useState('');
177
+ const [googleAdsConfigs, setGoogleAdsConfigs] = useState<
178
+ Array<{
179
+ id: string;
180
+ name: string;
181
+ active: boolean;
182
+ webhookKey: string;
183
+ defaultStatusId: string | null;
184
+ defaultAssignedUserId: string | null;
185
+ }>
186
+ >([]);
187
+ const [showGoogleAdsModal, setShowGoogleAdsModal] = useState(false);
188
+ const [editingGoogleAdsConfig, setEditingGoogleAdsConfig] = useState<string | null>(null);
189
+ const [googleAdsFormData, setGoogleAdsFormData] = useState<{
190
+ name: string;
191
+ active: boolean;
192
+ webhookKey: string;
193
+ defaultStatusId: string | null;
194
+ defaultAssignedUserId: string | null;
195
+ }>({
196
+ name: '',
197
+ active: true,
198
+ webhookKey: '',
199
+ defaultStatusId: null,
200
+ defaultAssignedUserId: null,
201
+ });
202
+
203
+ // État pour l'intégration Google Sheets (admin uniquement)
204
+ const [googleSheetLoading, setGoogleSheetLoading] = useState(true);
205
+ const [googleSheetSaving, setGoogleSheetSaving] = useState(false);
206
+ const [googleSheetSyncing, setGoogleSheetSyncing] = useState(false);
207
+ const [googleSheetError, setGoogleSheetError] = useState('');
208
+ const [googleSheetSuccess, setGoogleSheetSuccess] = useState('');
209
+ // Motifs de fermeture (ClosingReasons)
210
+ const [closingReasons, setClosingReasons] = useState<Array<{ id: string; name: string }>>([]);
211
+ const [closingReasonsLoading, setClosingReasonsLoading] = useState(false);
212
+ const [showClosingReasonForm, setShowClosingReasonForm] = useState(false);
213
+ const [closingReasonFormData, setClosingReasonFormData] = useState<{
214
+ id: string | null;
215
+ name: string;
216
+ }>({
217
+ id: null,
218
+ name: '',
219
+ });
220
+ const [closingReasonError, setClosingReasonError] = useState('');
221
+ const [closingReasonSuccess, setClosingReasonSuccess] = useState('');
222
+ const [googleSheetConfigs, setGoogleSheetConfigs] = useState<
223
+ Array<{
224
+ id: string;
225
+ name: string;
226
+ active: boolean;
227
+ spreadsheetId: string;
228
+ sheetName: string;
229
+ headerRow: number;
230
+ phoneColumn: string;
231
+ firstNameColumn: string | null;
232
+ lastNameColumn: string | null;
233
+ emailColumn: string | null;
234
+ cityColumn: string | null;
235
+ postalCodeColumn: string | null;
236
+ originColumn: string | null;
237
+ defaultStatusId: string | null;
238
+ defaultAssignedUserId: string | null;
239
+ }>
240
+ >([]);
241
+ const [showGoogleSheetModal, setShowGoogleSheetModal] = useState(false);
242
+ const [editingGoogleSheetConfig, setEditingGoogleSheetConfig] = useState<string | null>(null);
243
+ const [googleSheetStep, setGoogleSheetStep] = useState<1 | 2>(1);
244
+ const [googleSheetFormData, setGoogleSheetFormData] = useState<{
245
+ name: string;
246
+ active: boolean;
247
+ sheetUrl: string;
248
+ sheetName: string;
249
+ headerRow: string;
250
+ defaultStatusId: string | null;
251
+ defaultAssignedUserId: string | null;
252
+ }>({
253
+ name: '',
254
+ active: true,
255
+ sheetUrl: '',
256
+ sheetName: '',
257
+ headerRow: '1',
258
+ defaultStatusId: null,
259
+ defaultAssignedUserId: null,
260
+ });
261
+
262
+ // Structure pour les mappings de colonnes
263
+ interface ColumnMapping {
264
+ id: string;
265
+ columnName: string; // Nom de la colonne dans Google Sheets
266
+ action: 'map' | 'note' | 'ignore';
267
+ crmField?: string; // Champ CRM si action = 'map'
268
+ }
269
+
270
+ const [googleSheetMappings, setGoogleSheetMappings] = useState<ColumnMapping[]>([]);
271
+ const [googleSheetPreview, setGoogleSheetPreview] = useState<Array<Record<string, string>>>([]);
272
+ const [googleSheetHeaders, setGoogleSheetHeaders] = useState<string[]>([]);
273
+
274
+ // États pour la navigation
275
+ type MainSection = 'general' | 'app' | 'system' | 'integrations';
276
+ type GeneralSubSection = 'profile' | 'security' | 'company';
277
+ const [mainSection, setMainSection] = useState<MainSection>('general');
278
+ const [generalSubSection, setGeneralSubSection] = useState<GeneralSubSection>('profile');
279
+
280
+ // Mettre à jour le nom quand la session change
281
+ useEffect(() => {
282
+ if (session?.user?.name) {
283
+ setNameValue(session.user.name);
284
+ }
285
+ }, [session?.user?.name]);
286
+
287
+ // Charger les informations de l'entreprise au montage (si admin)
288
+ useEffect(() => {
289
+ if (isAdmin) {
290
+ const fetchCompanyData = async () => {
291
+ try {
292
+ setCompanyLoading(true);
293
+ const response = await fetch('/api/settings/company');
294
+ if (response.ok) {
295
+ const data = await response.json();
296
+ setCompanyData({
297
+ name: data.name || '',
298
+ address: data.address || '',
299
+ city: data.city || '',
300
+ postalCode: data.postalCode || '',
301
+ country: data.country || '',
302
+ phone: data.phone || '',
303
+ email: data.email || '',
304
+ website: data.website || '',
305
+ siret: data.siret || '',
306
+ vatNumber: data.vatNumber || '',
307
+ logo: data.logo || '',
308
+ });
309
+ }
310
+ } catch (error) {
311
+ console.error("Erreur lors du chargement des informations de l'entreprise:", error);
312
+ } finally {
313
+ setCompanyLoading(false);
314
+ }
315
+ };
316
+ fetchCompanyData();
317
+ }
318
+ }, [isAdmin]);
319
+
320
+ // Charger la configuration SMTP au montage
321
+ useEffect(() => {
322
+ const fetchSmtpData = async () => {
323
+ try {
324
+ setSmtpLoading(true);
325
+ const response = await fetch('/api/settings/smtp');
326
+ if (response.ok) {
327
+ const data = await response.json();
328
+ if (data) {
329
+ setSmtpData({
330
+ host: data.host || '',
331
+ port: data.port?.toString() || '587',
332
+ secure: data.secure || false,
333
+ username: data.username || '',
334
+ password: '', // Ne pas charger le mot de passe
335
+ fromEmail: data.fromEmail || '',
336
+ fromName: data.fromName || '',
337
+ signature: data.signature || '',
338
+ });
339
+ setSmtpConfigured(true);
340
+ // Injecter la signature existante dans l'éditeur si disponible
341
+ if (smtpSignatureEditorRef.current && data.signature) {
342
+ smtpSignatureEditorRef.current.injectHTML(data.signature);
343
+ }
344
+ } else {
345
+ setSmtpConfigured(false);
346
+ }
347
+ }
348
+ } catch (error) {
349
+ console.error('Erreur lors du chargement de la config SMTP:', error);
350
+ setSmtpConfigured(false);
351
+ } finally {
352
+ setSmtpLoading(false);
353
+ }
354
+ };
355
+ fetchSmtpData();
356
+ }, []);
357
+
358
+ // Synchroniser l'éditeur de signature quand la signature change dans le state
359
+ useEffect(() => {
360
+ if (smtpSignatureEditorRef.current && smtpData.signature) {
361
+ smtpSignatureEditorRef.current.injectHTML(smtpData.signature);
362
+ }
363
+ }, [smtpData.signature]);
364
+
365
+ // Charger les statuts au montage (si admin)
366
+ useEffect(() => {
367
+ if (isAdmin) {
368
+ const fetchStatuses = async () => {
369
+ try {
370
+ setStatusesLoading(true);
371
+ const response = await fetch('/api/settings/statuses');
372
+ if (response.ok) {
373
+ const data = await response.json();
374
+ setStatuses(data);
375
+ }
376
+ } catch (error) {
377
+ console.error('Erreur lors du chargement des statuts:', error);
378
+ } finally {
379
+ setStatusesLoading(false);
380
+ }
381
+ };
382
+ fetchStatuses();
383
+ }
384
+ }, [isAdmin]);
385
+
386
+ // Charger le statut de connexion Google
387
+ useEffect(() => {
388
+ const fetchGoogleAccount = async () => {
389
+ try {
390
+ setGoogleLoading(true);
391
+ const response = await fetch('/api/auth/google/status');
392
+ if (response.ok) {
393
+ const data = await response.json();
394
+ setGoogleAccount(data);
395
+ } else {
396
+ setGoogleAccount({ email: null, connected: false });
397
+ }
398
+ } catch (error) {
399
+ console.error('Erreur lors du chargement du compte Google:', error);
400
+ setGoogleAccount({ email: null, connected: false });
401
+ } finally {
402
+ setGoogleLoading(false);
403
+ }
404
+ };
405
+ fetchGoogleAccount();
406
+ }, []);
407
+
408
+ // Charger la configuration Meta Lead Ads et la liste des utilisateurs (admin uniquement)
409
+ useEffect(() => {
410
+ if (!isAdmin) return;
411
+
412
+ const fetchLeadConfigs = async () => {
413
+ try {
414
+ setMetaLeadLoading(true);
415
+ setGoogleAdsLoading(true);
416
+ setGoogleSheetLoading(true);
417
+ setMetaLeadError('');
418
+ setGoogleAdsError('');
419
+ setGoogleSheetError('');
420
+
421
+ const [
422
+ metaConfigRes,
423
+ googleAdsConfigRes,
424
+ googleSheetConfigRes,
425
+ closingReasonsRes,
426
+ usersRes,
427
+ ] = await Promise.all([
428
+ fetch('/api/settings/meta-leads'),
429
+ fetch('/api/settings/google-ads'),
430
+ fetch('/api/settings/google-sheet'),
431
+ fetch('/api/settings/closing-reasons'),
432
+ fetch('/api/users/list'),
433
+ ]);
434
+
435
+ if (metaConfigRes.ok) {
436
+ const configsData = await metaConfigRes.json();
437
+ setMetaLeadConfigs(Array.isArray(configsData) ? configsData : []);
438
+ }
439
+
440
+ if (googleAdsConfigRes.ok) {
441
+ const configsData = await googleAdsConfigRes.json();
442
+ setGoogleAdsConfigs(Array.isArray(configsData) ? configsData : []);
443
+ }
444
+
445
+ if (googleSheetConfigRes.ok) {
446
+ const configsData = await googleSheetConfigRes.json();
447
+ setGoogleSheetConfigs(Array.isArray(configsData) ? configsData : []);
448
+ }
449
+
450
+ if (closingReasonsRes.ok) {
451
+ const reasonsData = await closingReasonsRes.json();
452
+ setClosingReasons(Array.isArray(reasonsData) ? reasonsData : []);
453
+ }
454
+
455
+ if (usersRes.ok) {
456
+ const usersData = await usersRes.json();
457
+ setMetaLeadUsers(usersData);
458
+ }
459
+ } catch (error) {
460
+ console.error("Erreur lors du chargement de l'intégration Meta Lead Ads:", error);
461
+ setMetaLeadError("Erreur lors du chargement de l'intégration Meta Lead Ads");
462
+ } finally {
463
+ setMetaLeadLoading(false);
464
+ setGoogleAdsLoading(false);
465
+ setGoogleSheetLoading(false);
466
+ setClosingReasonsLoading(false);
467
+ }
468
+ };
469
+
470
+ fetchLeadConfigs();
471
+ }, [isAdmin]);
472
+
473
+ // Gérer les messages de succès/erreur depuis l'URL
474
+ useEffect(() => {
475
+ const params = new URLSearchParams(window.location.search);
476
+ const success = params.get('success');
477
+ const error = params.get('error');
478
+
479
+ if (success === 'google_connected') {
480
+ setGoogleAccount({ email: null, connected: true });
481
+ // Recharger le statut
482
+ fetch('/api/auth/google/status')
483
+ .then((res) => res.json())
484
+ .then((data) => setGoogleAccount(data))
485
+ .catch(() => {});
486
+ // Nettoyer l'URL
487
+ window.history.replaceState({}, '', '/settings');
488
+ }
489
+
490
+ if (error) {
491
+ console.error('Erreur Google:', error);
492
+ // Nettoyer l'URL
493
+ window.history.replaceState({}, '', '/settings');
494
+ }
495
+ }, []);
496
+
497
+ const handleGoogleConnect = () => {
498
+ window.location.href = '/api/auth/google';
499
+ };
500
+
501
+ const handleGoogleDisconnect = async () => {
502
+ if (!confirm('Êtes-vous sûr de vouloir déconnecter votre compte Google ?')) {
503
+ return;
504
+ }
505
+
506
+ try {
507
+ setGoogleDisconnecting(true);
508
+ const response = await fetch('/api/auth/google/disconnect', {
509
+ method: 'POST',
510
+ });
511
+
512
+ if (response.ok) {
513
+ setGoogleAccount({ email: null, connected: false });
514
+ } else {
515
+ const data = await response.json();
516
+ console.error('Erreur lors de la déconnexion:', data.error);
517
+ }
518
+ } catch (error) {
519
+ console.error('Erreur lors de la déconnexion Google:', error);
520
+ } finally {
521
+ setGoogleDisconnecting(false);
522
+ }
523
+ };
524
+
525
+ const handleCompanySubmit = async (e: React.FormEvent) => {
526
+ e.preventDefault();
527
+ setCompanyError('');
528
+ setCompanySuccess('');
529
+ setCompanySaving(true);
530
+
531
+ try {
532
+ const response = await fetch('/api/settings/company', {
533
+ method: 'PUT',
534
+ headers: { 'Content-Type': 'application/json' },
535
+ body: JSON.stringify(companyData),
536
+ });
537
+
538
+ const data = await response.json();
539
+
540
+ if (!response.ok) {
541
+ throw new Error(data.error || 'Erreur lors de la mise à jour');
542
+ }
543
+
544
+ setCompanySuccess("✅ Informations de l'entreprise mises à jour avec succès !");
545
+ setTimeout(() => {
546
+ setCompanySuccess('');
547
+ }, 5000);
548
+ } catch (err: any) {
549
+ setCompanyError(err.message);
550
+ } finally {
551
+ setCompanySaving(false);
552
+ }
553
+ };
554
+
555
+ const handleNameUpdate = async (e: React.FormEvent) => {
556
+ e.preventDefault();
557
+ setNameError('');
558
+ setNameSuccess('');
559
+
560
+ if (!nameValue.trim()) {
561
+ setNameError('Le nom ne peut pas être vide');
562
+ return;
563
+ }
564
+
565
+ setNameLoading(true);
566
+
567
+ try {
568
+ const response = await fetch('/api/settings/profile', {
569
+ method: 'PUT',
570
+ headers: { 'Content-Type': 'application/json' },
571
+ body: JSON.stringify({ name: nameValue.trim() }),
572
+ });
573
+
574
+ const data = await response.json();
575
+
576
+ if (!response.ok) {
577
+ throw new Error(data.error || 'Erreur lors de la mise à jour du nom');
578
+ }
579
+
580
+ setNameSuccess('✅ Nom mis à jour avec succès !');
581
+ setShowNameForm(false);
582
+
583
+ // Rafraîchir la page pour mettre à jour la session
584
+ router.refresh();
585
+
586
+ // Effacer le message après 5 secondes
587
+ setTimeout(() => {
588
+ setNameSuccess('');
589
+ }, 5000);
590
+ } catch (err: any) {
591
+ setNameError(err.message);
592
+ } finally {
593
+ setNameLoading(false);
594
+ }
595
+ };
596
+
597
+ const handleSmtpTest = async () => {
598
+ setSmtpTestResult(null);
599
+ setSmtpError('');
600
+ setSmtpTesting(true);
601
+
602
+ try {
603
+ // Récupérer la signature actuelle depuis l'éditeur (HTML)
604
+ let signatureHtml = smtpData.signature;
605
+ if (smtpSignatureEditorRef.current) {
606
+ try {
607
+ signatureHtml = await smtpSignatureEditorRef.current.getHTML();
608
+ } catch {
609
+ // on ignore l'erreur, on garde la valeur du state
610
+ }
611
+ }
612
+
613
+ const payload = { ...smtpData, signature: signatureHtml };
614
+
615
+ const response = await fetch('/api/settings/smtp/test', {
616
+ method: 'POST',
617
+ headers: { 'Content-Type': 'application/json' },
618
+ body: JSON.stringify(payload),
619
+ });
620
+
621
+ const data = await response.json();
622
+
623
+ if (data.success) {
624
+ setSmtpTestResult({
625
+ success: true,
626
+ message: data.message || 'Connexion SMTP réussie !',
627
+ });
628
+ setSmtpConfigured(true);
629
+
630
+ // Si le test est concluant, on enregistre automatiquement la configuration
631
+ try {
632
+ setSmtpSaving(true);
633
+ const saveResponse = await fetch('/api/settings/smtp', {
634
+ method: 'PUT',
635
+ headers: { 'Content-Type': 'application/json' },
636
+ body: JSON.stringify(payload),
637
+ });
638
+
639
+ const saveData = await saveResponse.json();
640
+
641
+ if (!saveResponse.ok) {
642
+ throw new Error(saveData.error || 'Erreur lors de la sauvegarde de la configuration');
643
+ }
644
+
645
+ setSmtpSuccess('✅ Configuration SMTP testée et sauvegardée avec succès !');
646
+ } catch (saveErr: any) {
647
+ // On affiche l’erreur de sauvegarde mais on garde le succès du test
648
+ setSmtpError(saveErr.message || 'La connexion fonctionne mais la sauvegarde a échoué.');
649
+ } finally {
650
+ setSmtpSaving(false);
651
+ }
652
+ } else {
653
+ setSmtpTestResult({
654
+ success: false,
655
+ message: data.message || 'Échec de la connexion SMTP',
656
+ });
657
+ setSmtpConfigured(false);
658
+ }
659
+ } catch (err: any) {
660
+ setSmtpTestResult({
661
+ success: false,
662
+ message: err.message || 'Erreur lors du test de connexion',
663
+ });
664
+ } finally {
665
+ setSmtpTesting(false);
666
+ }
667
+ };
668
+
669
+ const handleSmtpSubmit = async (e: React.FormEvent) => {
670
+ e.preventDefault();
671
+ setSmtpError('');
672
+ setSmtpSuccess('');
673
+ setSmtpSaving(true);
674
+
675
+ try {
676
+ // Récupérer la signature actuelle depuis l'éditeur (HTML)
677
+ let signatureHtml = smtpData.signature;
678
+ if (smtpSignatureEditorRef.current) {
679
+ try {
680
+ signatureHtml = await smtpSignatureEditorRef.current.getHTML();
681
+ } catch {
682
+ // on ignore l'erreur, on garde la valeur du state
683
+ }
684
+ }
685
+
686
+ const payload = { ...smtpData, signature: signatureHtml };
687
+
688
+ const response = await fetch('/api/settings/smtp', {
689
+ method: 'PUT',
690
+ headers: { 'Content-Type': 'application/json' },
691
+ body: JSON.stringify(payload),
692
+ });
693
+
694
+ const data = await response.json();
695
+
696
+ if (!response.ok) {
697
+ throw new Error(data.error || 'Erreur lors de la sauvegarde');
698
+ }
699
+
700
+ setSmtpSuccess('✅ Configuration SMTP sauvegardée avec succès !');
701
+ // Si le test a réussi précédemment, garder l'indicateur de configuration
702
+ if (smtpTestResult?.success) {
703
+ setSmtpConfigured(true);
704
+ }
705
+ setTimeout(() => {
706
+ setSmtpSuccess('');
707
+ }, 5000);
708
+ } catch (err: any) {
709
+ setSmtpError(err.message);
710
+ } finally {
711
+ setSmtpSaving(false);
712
+ }
713
+ };
714
+
715
+ const handleStatusSubmit = async (e: React.FormEvent) => {
716
+ e.preventDefault();
717
+ setStatusError('');
718
+ setStatusSuccess('');
719
+ setStatusSaving(true);
720
+
721
+ try {
722
+ const url = editingStatus
723
+ ? `/api/settings/statuses/${editingStatus.id}`
724
+ : '/api/settings/statuses';
725
+ const method = editingStatus ? 'PUT' : 'POST';
726
+
727
+ const response = await fetch(url, {
728
+ method,
729
+ headers: { 'Content-Type': 'application/json' },
730
+ body: JSON.stringify(statusFormData),
731
+ });
732
+
733
+ const data = await response.json();
734
+
735
+ if (!response.ok) {
736
+ throw new Error(data.error || 'Erreur lors de la sauvegarde');
737
+ }
738
+
739
+ setStatusSuccess(
740
+ editingStatus ? '✅ Statut modifié avec succès !' : '✅ Statut créé avec succès !',
741
+ );
742
+ setShowStatusForm(false);
743
+ setEditingStatus(null);
744
+ setStatusFormData({ name: '', color: '#3B82F6' });
745
+
746
+ // Recharger les statuts
747
+ const statusesResponse = await fetch('/api/settings/statuses');
748
+ if (statusesResponse.ok) {
749
+ const statusesData = await statusesResponse.json();
750
+ setStatuses(statusesData);
751
+ }
752
+
753
+ setTimeout(() => {
754
+ setStatusSuccess('');
755
+ }, 5000);
756
+ } catch (err: any) {
757
+ setStatusError(err.message);
758
+ } finally {
759
+ setStatusSaving(false);
760
+ }
761
+ };
762
+
763
+ const handleStatusDelete = async (id: string) => {
764
+ if (!confirm('Êtes-vous sûr de vouloir supprimer ce statut ?')) {
765
+ return;
766
+ }
767
+
768
+ try {
769
+ const response = await fetch(`/api/settings/statuses/${id}`, {
770
+ method: 'DELETE',
771
+ });
772
+
773
+ const data = await response.json();
774
+
775
+ if (!response.ok) {
776
+ throw new Error(data.error || 'Erreur lors de la suppression');
777
+ }
778
+
779
+ setStatusSuccess('✅ Statut supprimé avec succès !');
780
+ setTimeout(() => {
781
+ setStatusSuccess('');
782
+ }, 5000);
783
+
784
+ // Recharger les statuts
785
+ const statusesResponse = await fetch('/api/settings/statuses');
786
+ if (statusesResponse.ok) {
787
+ const statusesData = await statusesResponse.json();
788
+ setStatuses(statusesData);
789
+ }
790
+ } catch (err: any) {
791
+ setStatusError(err.message);
792
+ }
793
+ };
794
+
795
+ const handleStatusEdit = (status: Status) => {
796
+ setEditingStatus(status);
797
+ setStatusFormData({
798
+ name: status.name,
799
+ color: status.color,
800
+ });
801
+ setShowStatusForm(true);
802
+ setStatusError('');
803
+ setStatusSuccess('');
804
+ };
805
+
806
+ const handlePasswordChange = async (e: React.FormEvent) => {
807
+ e.preventDefault();
808
+ setPasswordError('');
809
+ setPasswordSuccess('');
810
+
811
+ if (passwordData.newPassword !== passwordData.confirmPassword) {
812
+ setPasswordError('Les nouveaux mots de passe ne correspondent pas');
813
+ return;
814
+ }
815
+
816
+ if (passwordData.newPassword.length < 6) {
817
+ setPasswordError('Le nouveau mot de passe doit contenir au moins 6 caractères');
818
+ return;
819
+ }
820
+
821
+ setPasswordLoading(true);
822
+
823
+ try {
824
+ const response = await fetch('/api/settings/change-password', {
825
+ method: 'POST',
826
+ headers: { 'Content-Type': 'application/json' },
827
+ body: JSON.stringify(passwordData),
828
+ });
829
+
830
+ const data = await response.json();
831
+
832
+ if (!response.ok) {
833
+ throw new Error(data.error || 'Erreur lors de la modification du mot de passe');
834
+ }
835
+
836
+ setPasswordSuccess('✅ Mot de passe modifié avec succès !');
837
+ setPasswordData({
838
+ currentPassword: '',
839
+ newPassword: '',
840
+ confirmPassword: '',
841
+ });
842
+ setShowPasswordForm(false);
843
+
844
+ // Effacer le message après 5 secondes
845
+ setTimeout(() => {
846
+ setPasswordSuccess('');
847
+ }, 5000);
848
+ } catch (err: any) {
849
+ setPasswordError(err.message);
850
+ } finally {
851
+ setPasswordLoading(false);
852
+ }
853
+ };
854
+
855
+ const handleMetaLeadSubmit = async (e: React.FormEvent) => {
856
+ e.preventDefault();
857
+ setMetaLeadError('');
858
+ setMetaLeadSuccess('');
859
+ setMetaLeadSaving(true);
860
+
861
+ try {
862
+ const url = editingMetaLeadConfig
863
+ ? `/api/settings/meta-leads/${editingMetaLeadConfig}`
864
+ : '/api/settings/meta-leads';
865
+ const method = editingMetaLeadConfig ? 'PUT' : 'POST';
866
+
867
+ const response = await fetch(url, {
868
+ method,
869
+ headers: { 'Content-Type': 'application/json' },
870
+ body: JSON.stringify(metaLeadFormData),
871
+ });
872
+
873
+ const data = await response.json();
874
+
875
+ if (!response.ok) {
876
+ throw new Error(data.error || 'Erreur lors de la sauvegarde de la configuration Meta');
877
+ }
878
+
879
+ setMetaLeadSuccess(
880
+ editingMetaLeadConfig
881
+ ? '✅ Configuration Meta Lead Ads mise à jour avec succès !'
882
+ : '✅ Configuration Meta Lead Ads créée avec succès !',
883
+ );
884
+ setShowMetaLeadModal(false);
885
+ setEditingMetaLeadConfig(null);
886
+ setMetaLeadFormData({
887
+ name: '',
888
+ active: true,
889
+ pageId: '',
890
+ accessToken: '',
891
+ verifyToken: '',
892
+ defaultStatusId: null,
893
+ defaultAssignedUserId: null,
894
+ });
895
+
896
+ // Recharger les configurations
897
+ const configsRes = await fetch('/api/settings/meta-leads');
898
+ if (configsRes.ok) {
899
+ const configsData = await configsRes.json();
900
+ setMetaLeadConfigs(Array.isArray(configsData) ? configsData : []);
901
+ }
902
+
903
+ setTimeout(() => setMetaLeadSuccess(''), 5000);
904
+ } catch (error: any) {
905
+ setMetaLeadError(error.message || 'Erreur lors de la sauvegarde de la configuration Meta');
906
+ } finally {
907
+ setMetaLeadSaving(false);
908
+ }
909
+ };
910
+
911
+ const handleGoogleAdsSubmit = async (e: React.FormEvent) => {
912
+ e.preventDefault();
913
+ setGoogleAdsError('');
914
+ setGoogleAdsSuccess('');
915
+ setGoogleAdsSaving(true);
916
+
917
+ try {
918
+ const url = editingGoogleAdsConfig
919
+ ? `/api/settings/google-ads/${editingGoogleAdsConfig}`
920
+ : '/api/settings/google-ads';
921
+ const method = editingGoogleAdsConfig ? 'PUT' : 'POST';
922
+
923
+ const response = await fetch(url, {
924
+ method,
925
+ headers: { 'Content-Type': 'application/json' },
926
+ body: JSON.stringify(googleAdsFormData),
927
+ });
928
+
929
+ const data = await response.json();
930
+
931
+ if (!response.ok) {
932
+ throw new Error(
933
+ data.error || 'Erreur lors de la sauvegarde de la configuration Google Ads',
934
+ );
935
+ }
936
+
937
+ setGoogleAdsSuccess(
938
+ editingGoogleAdsConfig
939
+ ? '✅ Configuration Google Ads mise à jour avec succès !'
940
+ : '✅ Configuration Google Ads créée avec succès !',
941
+ );
942
+ setShowGoogleAdsModal(false);
943
+ setEditingGoogleAdsConfig(null);
944
+ setGoogleAdsFormData({
945
+ name: '',
946
+ active: true,
947
+ webhookKey: '',
948
+ defaultStatusId: null,
949
+ defaultAssignedUserId: null,
950
+ });
951
+
952
+ // Recharger les configurations
953
+ const configsRes = await fetch('/api/settings/google-ads');
954
+ if (configsRes.ok) {
955
+ const configsData = await configsRes.json();
956
+ setGoogleAdsConfigs(Array.isArray(configsData) ? configsData : []);
957
+ }
958
+
959
+ setTimeout(() => setGoogleAdsSuccess(''), 5000);
960
+ } catch (error: any) {
961
+ setGoogleAdsError(error.message || 'Erreur lors de la sauvegarde de la configuration Google');
962
+ } finally {
963
+ setGoogleAdsSaving(false);
964
+ }
965
+ };
966
+
967
+ const handleGoogleSheetSubmit = async (e: React.FormEvent) => {
968
+ e.preventDefault();
969
+ setGoogleSheetError('');
970
+ setGoogleSheetSuccess('');
971
+ setGoogleSheetSaving(true);
972
+
973
+ try {
974
+ // Vérifier qu'au moins un mapping téléphone est configuré
975
+ const phoneMapping = googleSheetMappings.find(
976
+ (m) => m.action === 'map' && m.crmField === 'phone' && m.columnName.trim() !== '',
977
+ );
978
+
979
+ if (!phoneMapping) {
980
+ setGoogleSheetError('Le mapping du téléphone est obligatoire');
981
+ setGoogleSheetSaving(false);
982
+ return;
983
+ }
984
+
985
+ const url = editingGoogleSheetConfig
986
+ ? `/api/settings/google-sheet/${editingGoogleSheetConfig}`
987
+ : '/api/settings/google-sheet';
988
+ const method = editingGoogleSheetConfig ? 'PUT' : 'POST';
989
+
990
+ const response = await fetch(url, {
991
+ method,
992
+ headers: { 'Content-Type': 'application/json' },
993
+ body: JSON.stringify({
994
+ ...googleSheetFormData,
995
+ columnMappings: googleSheetMappings,
996
+ }),
997
+ });
998
+
999
+ const data = await response.json();
1000
+
1001
+ if (!response.ok) {
1002
+ throw new Error(
1003
+ data.error || 'Erreur lors de la sauvegarde de la configuration Google Sheets',
1004
+ );
1005
+ }
1006
+
1007
+ setGoogleSheetSuccess(
1008
+ editingGoogleSheetConfig
1009
+ ? '✅ Configuration Google Sheets mise à jour avec succès !'
1010
+ : '✅ Configuration Google Sheets créée avec succès !',
1011
+ );
1012
+ setShowGoogleSheetModal(false);
1013
+ setEditingGoogleSheetConfig(null);
1014
+ setGoogleSheetStep(1);
1015
+ setGoogleSheetFormData({
1016
+ name: '',
1017
+ active: true,
1018
+ sheetUrl: '',
1019
+ sheetName: '',
1020
+ headerRow: '1',
1021
+ defaultStatusId: null,
1022
+ defaultAssignedUserId: null,
1023
+ });
1024
+ setGoogleSheetMappings([]);
1025
+
1026
+ // Recharger les configurations
1027
+ const configsRes = await fetch('/api/settings/google-sheet');
1028
+ if (configsRes.ok) {
1029
+ const configsData = await configsRes.json();
1030
+ setGoogleSheetConfigs(Array.isArray(configsData) ? configsData : []);
1031
+ }
1032
+
1033
+ setTimeout(() => setGoogleSheetSuccess(''), 5000);
1034
+ } catch (error: any) {
1035
+ setGoogleSheetError(
1036
+ error.message || 'Erreur lors de la sauvegarde de la configuration Google Sheets',
1037
+ );
1038
+ } finally {
1039
+ setGoogleSheetSaving(false);
1040
+ }
1041
+ };
1042
+
1043
+ // Mapping automatique des colonnes Google Sheets (comme pour l'import CSV)
1044
+ const handleGoogleSheetAutoMap = async (): Promise<boolean> => {
1045
+ try {
1046
+ setGoogleSheetError('');
1047
+ setGoogleSheetSuccess('');
1048
+
1049
+ if (
1050
+ !googleSheetFormData.sheetUrl ||
1051
+ !googleSheetFormData.sheetName ||
1052
+ !googleSheetFormData.headerRow
1053
+ ) {
1054
+ setGoogleSheetError(
1055
+ 'Veuillez renseigner le lien du Google Sheet, le nom de l’onglet et la ligne des en-têtes avant de continuer.',
1056
+ );
1057
+ return false;
1058
+ }
1059
+
1060
+ const response = await fetch('/api/settings/google-sheet/auto-map', {
1061
+ method: 'POST',
1062
+ headers: { 'Content-Type': 'application/json' },
1063
+ body: JSON.stringify({
1064
+ sheetUrl: googleSheetFormData.sheetUrl,
1065
+ sheetName: googleSheetFormData.sheetName,
1066
+ headerRow: googleSheetFormData.headerRow || '1',
1067
+ }),
1068
+ });
1069
+
1070
+ const data = await response.json();
1071
+
1072
+ if (!response.ok) {
1073
+ throw new Error(
1074
+ data.error || 'Erreur lors du mapping automatique des colonnes Google Sheets',
1075
+ );
1076
+ }
1077
+
1078
+ const headers = data.headers || [];
1079
+ const autoMapping = data.mapping || {};
1080
+ const preview = data.preview || [];
1081
+
1082
+ // Stocker les headers et l'aperçu
1083
+ setGoogleSheetHeaders(headers);
1084
+ setGoogleSheetPreview(preview);
1085
+
1086
+ // Initialiser les mappings avec les colonnes trouvées
1087
+ const initialMappings: ColumnMapping[] = [];
1088
+
1089
+ // Créer un mapping pour chaque colonne trouvée
1090
+ headers.forEach((header: string, index: number) => {
1091
+ if (!header) return;
1092
+
1093
+ // L'API auto-map retourne les index de colonnes (A, B, C), mais on utilise les noms réels
1094
+ // Chercher si cette colonne (par index) a été auto-mappée
1095
+ const columnLetter = indexToColumn(index);
1096
+ const mappedField = Object.entries(autoMapping).find(
1097
+ ([, columnLetterFromMapping]) => columnLetterFromMapping === columnLetter,
1098
+ )?.[0];
1099
+
1100
+ if (mappedField) {
1101
+ // Convertir l'ancien format vers le nouveau
1102
+ const crmFieldMap: Record<string, string> = {
1103
+ phoneColumn: 'phone',
1104
+ firstNameColumn: 'firstName',
1105
+ lastNameColumn: 'lastName',
1106
+ emailColumn: 'email',
1107
+ cityColumn: 'city',
1108
+ postalCodeColumn: 'postalCode',
1109
+ originColumn: 'origin',
1110
+ };
1111
+
1112
+ initialMappings.push({
1113
+ id: `mapping-${Date.now()}-${Math.random()}`,
1114
+ columnName: header, // Utiliser le nom réel de la colonne
1115
+ action: 'map',
1116
+ crmField: crmFieldMap[mappedField] || undefined,
1117
+ });
1118
+ } else {
1119
+ // Colonne non mappée automatiquement, laisser l'utilisateur choisir
1120
+ initialMappings.push({
1121
+ id: `mapping-${Date.now()}-${Math.random()}`,
1122
+ columnName: header,
1123
+ action: 'ignore',
1124
+ });
1125
+ }
1126
+ });
1127
+
1128
+ setGoogleSheetMappings(initialMappings);
1129
+
1130
+ setGoogleSheetSuccess(
1131
+ '✅ Colonnes détectées. Configurez maintenant le mapping des colonnes.',
1132
+ );
1133
+ return true;
1134
+ } catch (error: any) {
1135
+ console.error('Erreur lors du mapping automatique Google Sheets:', error);
1136
+ setGoogleSheetError(
1137
+ error.message || 'Erreur lors du mapping automatique des colonnes Google Sheets',
1138
+ );
1139
+ return false;
1140
+ }
1141
+ };
1142
+
1143
+ const handleGoogleSheetSync = async () => {
1144
+ setGoogleSheetError('');
1145
+ setGoogleSheetSuccess('');
1146
+ setGoogleSheetSyncing(true);
1147
+
1148
+ try {
1149
+ const response = await fetch('/api/integrations/google-sheet/sync', {
1150
+ method: 'POST',
1151
+ });
1152
+
1153
+ const data = await response.json();
1154
+
1155
+ if (!response.ok) {
1156
+ throw new Error(data.error || 'Erreur lors de la synchronisation Google Sheets');
1157
+ }
1158
+
1159
+ const totalImported = data.totalImported || data.imported || 0;
1160
+ const totalUpdated = data.totalUpdated || data.updated || 0;
1161
+ const totalSkipped = data.totalSkipped || data.skipped || 0;
1162
+ setGoogleSheetSuccess(
1163
+ `✅ Synchronisation terminée : ${totalImported} nouveau(x) contact(s), ${totalUpdated} mis à jour, ${totalSkipped} ignoré(s).`,
1164
+ );
1165
+ setTimeout(() => setGoogleSheetSuccess(''), 8000);
1166
+ } catch (error: any) {
1167
+ setGoogleSheetError(
1168
+ error.message || 'Erreur lors de la synchronisation des contacts Google Sheets',
1169
+ );
1170
+ } finally {
1171
+ setGoogleSheetSyncing(false);
1172
+ }
1173
+ };
1174
+
1175
+ // Fonctions pour gérer les configurations Meta Lead Ads
1176
+ const handleEditMetaLead = (config: (typeof metaLeadConfigs)[0]) => {
1177
+ setEditingMetaLeadConfig(config.id);
1178
+ setMetaLeadFormData({
1179
+ name: config.name,
1180
+ active: config.active,
1181
+ pageId: config.pageId,
1182
+ accessToken: '', // Ne pas charger le token
1183
+ verifyToken: config.verifyToken,
1184
+ defaultStatusId: config.defaultStatusId,
1185
+ defaultAssignedUserId: config.defaultAssignedUserId,
1186
+ });
1187
+ setShowMetaLeadModal(true);
1188
+ setMetaLeadError('');
1189
+ setMetaLeadSuccess('');
1190
+ };
1191
+
1192
+ const handleDeleteMetaLead = async (id: string) => {
1193
+ if (!confirm('Êtes-vous sûr de vouloir supprimer cette configuration ?')) {
1194
+ return;
1195
+ }
1196
+
1197
+ try {
1198
+ const response = await fetch(`/api/settings/meta-leads/${id}`, {
1199
+ method: 'DELETE',
1200
+ });
1201
+
1202
+ if (!response.ok) {
1203
+ const data = await response.json();
1204
+ throw new Error(data.error || 'Erreur lors de la suppression');
1205
+ }
1206
+
1207
+ setMetaLeadSuccess('✅ Configuration supprimée avec succès !');
1208
+ setTimeout(() => setMetaLeadSuccess(''), 5000);
1209
+
1210
+ // Recharger les configurations
1211
+ const configsRes = await fetch('/api/settings/meta-leads');
1212
+ if (configsRes.ok) {
1213
+ const configsData = await configsRes.json();
1214
+ setMetaLeadConfigs(Array.isArray(configsData) ? configsData : []);
1215
+ }
1216
+ } catch (error: any) {
1217
+ setMetaLeadError(error.message || 'Erreur lors de la suppression');
1218
+ }
1219
+ };
1220
+
1221
+ // Fonctions pour gérer les configurations Google Ads
1222
+ const handleEditGoogleAds = (config: (typeof googleAdsConfigs)[0]) => {
1223
+ setEditingGoogleAdsConfig(config.id);
1224
+ setGoogleAdsFormData({
1225
+ name: config.name,
1226
+ active: config.active,
1227
+ webhookKey: config.webhookKey,
1228
+ defaultStatusId: config.defaultStatusId,
1229
+ defaultAssignedUserId: config.defaultAssignedUserId,
1230
+ });
1231
+ setShowGoogleAdsModal(true);
1232
+ setGoogleAdsError('');
1233
+ setGoogleAdsSuccess('');
1234
+ };
1235
+
1236
+ const handleDeleteGoogleAds = async (id: string) => {
1237
+ if (!confirm('Êtes-vous sûr de vouloir supprimer cette configuration ?')) {
1238
+ return;
1239
+ }
1240
+
1241
+ try {
1242
+ const response = await fetch(`/api/settings/google-ads/${id}`, {
1243
+ method: 'DELETE',
1244
+ });
1245
+
1246
+ if (!response.ok) {
1247
+ const data = await response.json();
1248
+ throw new Error(data.error || 'Erreur lors de la suppression');
1249
+ }
1250
+
1251
+ setGoogleAdsSuccess('✅ Configuration supprimée avec succès !');
1252
+ setTimeout(() => setGoogleAdsSuccess(''), 5000);
1253
+
1254
+ // Recharger les configurations
1255
+ const configsRes = await fetch('/api/settings/google-ads');
1256
+ if (configsRes.ok) {
1257
+ const configsData = await configsRes.json();
1258
+ setGoogleAdsConfigs(Array.isArray(configsData) ? configsData : []);
1259
+ }
1260
+ } catch (error: any) {
1261
+ setGoogleAdsError(error.message || 'Erreur lors de la suppression');
1262
+ }
1263
+ };
1264
+
1265
+ // Fonctions pour gérer les configurations Google Sheets
1266
+ const handleEditGoogleSheet = (config: (typeof googleSheetConfigs)[0]) => {
1267
+ setEditingGoogleSheetConfig(config.id);
1268
+ setGoogleSheetFormData({
1269
+ name: config.name,
1270
+ active: config.active,
1271
+ sheetUrl: config.spreadsheetId
1272
+ ? `https://docs.google.com/spreadsheets/d/${config.spreadsheetId}/edit`
1273
+ : '',
1274
+ sheetName: config.sheetName,
1275
+ headerRow: config.headerRow.toString(),
1276
+ defaultStatusId: config.defaultStatusId,
1277
+ defaultAssignedUserId: config.defaultAssignedUserId,
1278
+ });
1279
+
1280
+ // Charger les mappings existants (si disponibles) ou convertir l'ancien format
1281
+ if ((config as any).columnMappings && Array.isArray((config as any).columnMappings)) {
1282
+ setGoogleSheetMappings((config as any).columnMappings);
1283
+ } else {
1284
+ // Convertir l'ancien format vers le nouveau
1285
+ const mappings: ColumnMapping[] = [];
1286
+ const oldMappings = [
1287
+ { column: config.phoneColumn, field: 'phone' },
1288
+ { column: config.firstNameColumn, field: 'firstName' },
1289
+ { column: config.lastNameColumn, field: 'lastName' },
1290
+ { column: config.emailColumn, field: 'email' },
1291
+ { column: config.cityColumn, field: 'city' },
1292
+ { column: config.postalCodeColumn, field: 'postalCode' },
1293
+ { column: config.originColumn, field: 'origin' },
1294
+ ];
1295
+
1296
+ oldMappings.forEach(({ column, field }) => {
1297
+ if (column) {
1298
+ mappings.push({
1299
+ id: `mapping-${Date.now()}-${Math.random()}`,
1300
+ columnName: column,
1301
+ action: 'map',
1302
+ crmField: field,
1303
+ });
1304
+ }
1305
+ });
1306
+
1307
+ setGoogleSheetMappings(mappings);
1308
+ }
1309
+
1310
+ setGoogleSheetStep(2); // Aller directement à l'étape 2 pour l'édition
1311
+ setShowGoogleSheetModal(true);
1312
+ setGoogleSheetError('');
1313
+ setGoogleSheetSuccess('');
1314
+ };
1315
+
1316
+ const handleDeleteGoogleSheet = async (id: string) => {
1317
+ if (!confirm('Êtes-vous sûr de vouloir supprimer cette configuration ?')) {
1318
+ return;
1319
+ }
1320
+
1321
+ try {
1322
+ const response = await fetch(`/api/settings/google-sheet/${id}`, {
1323
+ method: 'DELETE',
1324
+ });
1325
+
1326
+ if (!response.ok) {
1327
+ const data = await response.json();
1328
+ throw new Error(data.error || 'Erreur lors de la suppression');
1329
+ }
1330
+
1331
+ setGoogleSheetSuccess('✅ Configuration supprimée avec succès !');
1332
+ setTimeout(() => setGoogleSheetSuccess(''), 5000);
1333
+
1334
+ // Recharger les configurations
1335
+ const configsRes = await fetch('/api/settings/google-sheet');
1336
+ if (configsRes.ok) {
1337
+ const configsData = await configsRes.json();
1338
+ setGoogleSheetConfigs(Array.isArray(configsData) ? configsData : []);
1339
+ }
1340
+ } catch (error: any) {
1341
+ setGoogleSheetError(error.message || 'Erreur lors de la suppression');
1342
+ }
1343
+ };
1344
+
1345
+ return (
1346
+ <div className="bg-crms-bg flex h-full flex-col">
1347
+ {/* Header avec breadcrumbs */}
1348
+ <div className="border-b border-gray-200 bg-white px-4 py-4 sm:px-6 lg:px-8">
1349
+ <div className="flex items-center justify-between">
1350
+ <div>
1351
+ <h1 className="text-2xl font-bold text-gray-900">Paramètres</h1>
1352
+ <p className="mt-1 text-sm text-gray-500">Home {'>'} Paramètres</p>
1353
+ </div>
1354
+ <div className="flex items-center gap-2">
1355
+ <button
1356
+ type="button"
1357
+ onClick={() => window.location.reload()}
1358
+ className="cursor-pointer rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
1359
+ aria-label="Actualiser"
1360
+ >
1361
+ <RefreshCw className="h-5 w-5" />
1362
+ </button>
1363
+ <button
1364
+ type="button"
1365
+ className="cursor-pointer rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
1366
+ aria-label="Paramètres"
1367
+ >
1368
+ <Settings className="h-5 w-5" />
1369
+ </button>
1370
+ </div>
1371
+ </div>
1372
+ </div>
1373
+
1374
+ {/* Navbar horizontale avec sections principales */}
1375
+ <div className="border-b border-gray-200 bg-white px-4 sm:px-6 lg:px-8">
1376
+ <div className="flex gap-1 overflow-x-auto">
1377
+ <button
1378
+ type="button"
1379
+ onClick={() => setMainSection('general')}
1380
+ className={cn(
1381
+ 'flex cursor-pointer items-center gap-2 border-b-2 px-4 py-3 text-sm font-medium whitespace-nowrap transition-colors',
1382
+ mainSection === 'general'
1383
+ ? 'border-indigo-600 text-indigo-600'
1384
+ : 'border-transparent text-gray-600 hover:border-gray-300 hover:text-gray-900',
1385
+ )}
1386
+ >
1387
+ <Settings className="h-4 w-4" />
1388
+ Paramètres Généraux
1389
+ </button>
1390
+ {isAdmin && (
1391
+ <button
1392
+ type="button"
1393
+ onClick={() => setMainSection('app')}
1394
+ className={cn(
1395
+ 'flex cursor-pointer items-center gap-2 border-b-2 px-4 py-3 text-sm font-medium whitespace-nowrap transition-colors',
1396
+ mainSection === 'app'
1397
+ ? 'border-indigo-600 text-indigo-600'
1398
+ : 'border-transparent text-gray-600 hover:border-gray-300 hover:text-gray-900',
1399
+ )}
1400
+ >
1401
+ <Grid3x3 className="h-4 w-4" />
1402
+ Paramètres de l&apos;Application
1403
+ </button>
1404
+ )}
1405
+ <button
1406
+ type="button"
1407
+ onClick={() => setMainSection('system')}
1408
+ className={cn(
1409
+ 'flex cursor-pointer items-center gap-2 border-b-2 px-4 py-3 text-sm font-medium whitespace-nowrap transition-colors',
1410
+ mainSection === 'system'
1411
+ ? 'border-indigo-600 text-indigo-600'
1412
+ : 'border-transparent text-gray-600 hover:border-gray-300 hover:text-gray-900',
1413
+ )}
1414
+ >
1415
+ <Monitor className="h-4 w-4" />
1416
+ Paramètres Système
1417
+ </button>
1418
+ {isAdmin && (
1419
+ <button
1420
+ type="button"
1421
+ onClick={() => setMainSection('integrations')}
1422
+ className={cn(
1423
+ 'flex cursor-pointer items-center gap-2 border-b-2 px-4 py-3 text-sm font-medium whitespace-nowrap transition-colors',
1424
+ mainSection === 'integrations'
1425
+ ? 'border-indigo-600 text-indigo-600'
1426
+ : 'border-transparent text-gray-600 hover:border-gray-300 hover:text-gray-900',
1427
+ )}
1428
+ >
1429
+ <Plug className="h-4 w-4" />
1430
+ Intégrations
1431
+ </button>
1432
+ )}
1433
+ </div>
1434
+ </div>
1435
+
1436
+ {/* Content avec panneau principal */}
1437
+ <div className="flex flex-1 overflow-hidden">
1438
+ {/* Panneau de contenu principal */}
1439
+ <div className="bg-crms-bg flex-1 overflow-y-auto p-4 sm:p-6 lg:p-8">
1440
+ <div className="mx-auto max-w-4xl space-y-4 sm:space-y-6">
1441
+ {/* Section Paramètres Généraux */}
1442
+ {mainSection === 'general' && (
1443
+ <div className="space-y-6">
1444
+ {/* Section Profil */}
1445
+ <div className="rounded-lg bg-white p-6 shadow-sm">
1446
+ <div className="mb-1 flex items-center justify-between">
1447
+ <div>
1448
+ <h2 className="text-lg font-bold text-gray-900">Profil</h2>
1449
+ </div>
1450
+ </div>
1451
+ <p className="mb-6 text-sm text-gray-600">Gérez vos informations personnelles</p>
1452
+
1453
+ <div className="space-y-4">
1454
+ {/* Nom - Modifiable */}
1455
+ <div className="border-b border-gray-100 pb-4">
1456
+ {!showNameForm ? (
1457
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
1458
+ <div className="min-w-0 flex-1">
1459
+ <p className="font-medium text-gray-900">Nom</p>
1460
+ <p className="mt-1 truncate text-sm text-gray-600">
1461
+ {session?.user?.name || 'Non défini'}
1462
+ </p>
1463
+ </div>
1464
+ <button
1465
+ onClick={() => {
1466
+ setShowNameForm(true);
1467
+ setNameValue(session?.user?.name || '');
1468
+ setNameError('');
1469
+ setNameSuccess('');
1470
+ }}
1471
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
1472
+ >
1473
+ Modifier
1474
+ </button>
1475
+ </div>
1476
+ ) : (
1477
+ <form onSubmit={handleNameUpdate} className="space-y-4">
1478
+ {nameSuccess && (
1479
+ <div className="rounded-lg bg-green-50 p-4 text-sm text-green-600">
1480
+ {nameSuccess}
1481
+ </div>
1482
+ )}
1483
+
1484
+ {nameError && (
1485
+ <div className="rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
1486
+ {nameError}
1487
+ </div>
1488
+ )}
1489
+
1490
+ <div>
1491
+ <label className="block text-sm font-medium text-gray-700">Nom</label>
1492
+ <input
1493
+ type="text"
1494
+ required
1495
+ value={nameValue}
1496
+ onChange={(e) => setNameValue(e.target.value)}
1497
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1498
+ placeholder="Votre nom"
1499
+ />
1500
+ </div>
1501
+
1502
+ <div className="flex flex-col gap-3 sm:flex-row">
1503
+ <button
1504
+ type="submit"
1505
+ disabled={nameLoading}
1506
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
1507
+ >
1508
+ {nameLoading ? 'Enregistrement...' : 'Enregistrer'}
1509
+ </button>
1510
+ <button
1511
+ type="button"
1512
+ onClick={() => {
1513
+ setShowNameForm(false);
1514
+ setNameValue(session?.user?.name || '');
1515
+ setNameError('');
1516
+ setNameSuccess('');
1517
+ }}
1518
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
1519
+ >
1520
+ Annuler
1521
+ </button>
1522
+ </div>
1523
+ </form>
1524
+ )}
1525
+ </div>
1526
+
1527
+ {/* Email - Lecture seule */}
1528
+ <div className="pb-0">
1529
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
1530
+ <div className="min-w-0 flex-1">
1531
+ <p className="font-medium text-gray-900">Email</p>
1532
+ <p className="mt-1 truncate text-sm text-gray-600">
1533
+ {session?.user?.email || 'Non défini'}
1534
+ </p>
1535
+ <p className="mt-1 text-xs text-gray-500">
1536
+ L'email ne peut pas être modifié
1537
+ </p>
1538
+ </div>
1539
+ </div>
1540
+ </div>
1541
+ </div>
1542
+ </div>
1543
+
1544
+ {/* Section Sécurité */}
1545
+ <div className="rounded-lg bg-white p-6 shadow-sm">
1546
+ <h2 className="mb-6 text-lg font-bold text-gray-900">Sécurité</h2>
1547
+ <div className="mt-4 sm:mt-6">
1548
+ {!showPasswordForm ? (
1549
+ <div className="flex flex-col gap-3 border-b border-gray-100 pb-4 sm:flex-row sm:items-center sm:justify-between">
1550
+ <div className="min-w-0 flex-1">
1551
+ <p className="font-medium text-gray-900">Mot de passe</p>
1552
+ <p className="mt-1 text-sm text-gray-600">••••••••</p>
1553
+ </div>
1554
+ <button
1555
+ onClick={() => setShowPasswordForm(true)}
1556
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
1557
+ >
1558
+ Modifier
1559
+ </button>
1560
+ </div>
1561
+ ) : (
1562
+ <form onSubmit={handlePasswordChange} className="space-y-4">
1563
+ {passwordSuccess && (
1564
+ <div className="rounded-lg bg-green-50 p-4 text-sm text-green-600">
1565
+ {passwordSuccess}
1566
+ </div>
1567
+ )}
1568
+
1569
+ {passwordError && (
1570
+ <div className="rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
1571
+ {passwordError}
1572
+ </div>
1573
+ )}
1574
+
1575
+ <div>
1576
+ <label className="block text-sm font-medium text-gray-700">
1577
+ Mot de passe actuel
1578
+ </label>
1579
+ <input
1580
+ type="password"
1581
+ required
1582
+ value={passwordData.currentPassword}
1583
+ onChange={(e) =>
1584
+ setPasswordData({
1585
+ ...passwordData,
1586
+ currentPassword: e.target.value,
1587
+ })
1588
+ }
1589
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1590
+ placeholder="••••••••"
1591
+ />
1592
+ </div>
1593
+
1594
+ <div>
1595
+ <label className="block text-sm font-medium text-gray-700">
1596
+ Nouveau mot de passe
1597
+ </label>
1598
+ <input
1599
+ type="password"
1600
+ required
1601
+ minLength={6}
1602
+ value={passwordData.newPassword}
1603
+ onChange={(e) =>
1604
+ setPasswordData({
1605
+ ...passwordData,
1606
+ newPassword: e.target.value,
1607
+ })
1608
+ }
1609
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1610
+ placeholder="••••••••"
1611
+ />
1612
+ <p className="mt-1 text-xs text-gray-500">Minimum 6 caractères</p>
1613
+ </div>
1614
+
1615
+ <div>
1616
+ <label className="block text-sm font-medium text-gray-700">
1617
+ Confirmer le nouveau mot de passe
1618
+ </label>
1619
+ <input
1620
+ type="password"
1621
+ required
1622
+ minLength={6}
1623
+ value={passwordData.confirmPassword}
1624
+ onChange={(e) =>
1625
+ setPasswordData({
1626
+ ...passwordData,
1627
+ confirmPassword: e.target.value,
1628
+ })
1629
+ }
1630
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1631
+ placeholder="••••••••"
1632
+ />
1633
+ </div>
1634
+
1635
+ <div className="flex flex-col gap-3 sm:flex-row">
1636
+ <button
1637
+ type="submit"
1638
+ disabled={passwordLoading}
1639
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
1640
+ >
1641
+ {passwordLoading ? 'Modification...' : 'Modifier le mot de passe'}
1642
+ </button>
1643
+ <button
1644
+ type="button"
1645
+ onClick={() => {
1646
+ setShowPasswordForm(false);
1647
+ setPasswordData({
1648
+ currentPassword: '',
1649
+ newPassword: '',
1650
+ confirmPassword: '',
1651
+ });
1652
+ setPasswordError('');
1653
+ setPasswordSuccess('');
1654
+ }}
1655
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
1656
+ >
1657
+ Annuler
1658
+ </button>
1659
+ </div>
1660
+ </form>
1661
+ )}
1662
+ </div>
1663
+ </div>
1664
+
1665
+ {/* Section Entreprise */}
1666
+ {isAdmin && (
1667
+ <div className="rounded-lg bg-white p-6 shadow-sm">
1668
+ <h2 className="mb-1 text-lg font-bold text-gray-900">
1669
+ Informations de l&apos;entreprise
1670
+ </h2>
1671
+ <p className="mb-6 text-sm text-gray-600">
1672
+ Gérez les informations de votre entreprise (visible uniquement par les
1673
+ administrateurs)
1674
+ </p>
1675
+
1676
+ {companySuccess && (
1677
+ <div className="mt-4 rounded-lg border border-green-200 bg-green-50 p-4">
1678
+ <div className="flex items-center">
1679
+ <svg
1680
+ className="mr-3 h-5 w-5 text-green-600"
1681
+ fill="none"
1682
+ stroke="currentColor"
1683
+ viewBox="0 0 24 24"
1684
+ >
1685
+ <path
1686
+ strokeLinecap="round"
1687
+ strokeLinejoin="round"
1688
+ strokeWidth={2}
1689
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
1690
+ />
1691
+ </svg>
1692
+ <p className="text-sm font-medium text-green-800">{companySuccess}</p>
1693
+ <button
1694
+ onClick={() => setCompanySuccess('')}
1695
+ className="ml-auto cursor-pointer text-green-600 hover:text-green-800"
1696
+ >
1697
+ <svg
1698
+ className="h-5 w-5"
1699
+ fill="none"
1700
+ stroke="currentColor"
1701
+ viewBox="0 0 24 24"
1702
+ >
1703
+ <path
1704
+ strokeLinecap="round"
1705
+ strokeLinejoin="round"
1706
+ strokeWidth={2}
1707
+ d="M6 18L18 6M6 6l12 12"
1708
+ />
1709
+ </svg>
1710
+ </button>
1711
+ </div>
1712
+ </div>
1713
+ )}
1714
+
1715
+ {companyError && (
1716
+ <div className="mt-4 rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
1717
+ {companyError}
1718
+ </div>
1719
+ )}
1720
+
1721
+ {companyLoading ? (
1722
+ <div className="mt-6 text-center text-gray-500">Chargement...</div>
1723
+ ) : (
1724
+ <form onSubmit={handleCompanySubmit} className="mt-6 space-y-4">
1725
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
1726
+ <div>
1727
+ <label className="block text-sm font-medium text-gray-700">
1728
+ Nom de l'entreprise *
1729
+ </label>
1730
+ <input
1731
+ type="text"
1732
+ required
1733
+ value={companyData.name}
1734
+ onChange={(e) =>
1735
+ setCompanyData({ ...companyData, name: e.target.value })
1736
+ }
1737
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1738
+ placeholder="Nom de l'entreprise"
1739
+ />
1740
+ </div>
1741
+
1742
+ <div>
1743
+ <label className="block text-sm font-medium text-gray-700">Email</label>
1744
+ <input
1745
+ type="email"
1746
+ value={companyData.email}
1747
+ onChange={(e) =>
1748
+ setCompanyData({ ...companyData, email: e.target.value })
1749
+ }
1750
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1751
+ placeholder="contact@entreprise.com"
1752
+ />
1753
+ </div>
1754
+
1755
+ <div>
1756
+ <label className="block text-sm font-medium text-gray-700">
1757
+ Téléphone
1758
+ </label>
1759
+ <input
1760
+ type="tel"
1761
+ value={companyData.phone}
1762
+ onChange={(e) =>
1763
+ setCompanyData({ ...companyData, phone: e.target.value })
1764
+ }
1765
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1766
+ placeholder="+33 1 23 45 67 89"
1767
+ />
1768
+ </div>
1769
+
1770
+ <div>
1771
+ <label className="block text-sm font-medium text-gray-700">
1772
+ Site web
1773
+ </label>
1774
+ <input
1775
+ type="url"
1776
+ value={companyData.website}
1777
+ onChange={(e) =>
1778
+ setCompanyData({ ...companyData, website: e.target.value })
1779
+ }
1780
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1781
+ placeholder="https://www.entreprise.com"
1782
+ />
1783
+ </div>
1784
+
1785
+ <div className="md:col-span-2">
1786
+ <label className="block text-sm font-medium text-gray-700">
1787
+ Adresse
1788
+ </label>
1789
+ <input
1790
+ type="text"
1791
+ value={companyData.address}
1792
+ onChange={(e) =>
1793
+ setCompanyData({ ...companyData, address: e.target.value })
1794
+ }
1795
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1796
+ placeholder="123 Rue de la République"
1797
+ />
1798
+ </div>
1799
+
1800
+ <div>
1801
+ <label className="block text-sm font-medium text-gray-700">Ville</label>
1802
+ <input
1803
+ type="text"
1804
+ value={companyData.city}
1805
+ onChange={(e) =>
1806
+ setCompanyData({ ...companyData, city: e.target.value })
1807
+ }
1808
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1809
+ placeholder="Paris"
1810
+ />
1811
+ </div>
1812
+
1813
+ <div>
1814
+ <label className="block text-sm font-medium text-gray-700">
1815
+ Code postal
1816
+ </label>
1817
+ <input
1818
+ type="text"
1819
+ value={companyData.postalCode}
1820
+ onChange={(e) =>
1821
+ setCompanyData({ ...companyData, postalCode: e.target.value })
1822
+ }
1823
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1824
+ placeholder="75001"
1825
+ />
1826
+ </div>
1827
+
1828
+ <div>
1829
+ <label className="block text-sm font-medium text-gray-700">Pays</label>
1830
+ <input
1831
+ type="text"
1832
+ value={companyData.country}
1833
+ onChange={(e) =>
1834
+ setCompanyData({ ...companyData, country: e.target.value })
1835
+ }
1836
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1837
+ placeholder="France"
1838
+ />
1839
+ </div>
1840
+
1841
+ <div>
1842
+ <label className="block text-sm font-medium text-gray-700">SIRET</label>
1843
+ <input
1844
+ type="text"
1845
+ value={companyData.siret}
1846
+ onChange={(e) =>
1847
+ setCompanyData({ ...companyData, siret: e.target.value })
1848
+ }
1849
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1850
+ placeholder="123 456 789 00012"
1851
+ />
1852
+ </div>
1853
+
1854
+ <div className="md:col-span-2">
1855
+ <label className="block text-sm font-medium text-gray-700">
1856
+ Numéro TVA
1857
+ </label>
1858
+ <input
1859
+ type="text"
1860
+ value={companyData.vatNumber}
1861
+ onChange={(e) =>
1862
+ setCompanyData({ ...companyData, vatNumber: e.target.value })
1863
+ }
1864
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1865
+ placeholder="FR12 345678901"
1866
+ />
1867
+ </div>
1868
+
1869
+ <div className="md:col-span-2">
1870
+ <label className="block text-sm font-medium text-gray-700">
1871
+ URL du logo
1872
+ </label>
1873
+ <input
1874
+ type="url"
1875
+ value={companyData.logo}
1876
+ onChange={(e) =>
1877
+ setCompanyData({ ...companyData, logo: e.target.value })
1878
+ }
1879
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1880
+ placeholder="https://example.com/logo.png"
1881
+ />
1882
+ </div>
1883
+ </div>
1884
+
1885
+ <div className="flex justify-end gap-3 pt-4">
1886
+ <button
1887
+ type="submit"
1888
+ disabled={companySaving}
1889
+ className="cursor-pointer rounded-lg bg-indigo-600 px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
1890
+ >
1891
+ {companySaving ? 'Enregistrement...' : 'Enregistrer'}
1892
+ </button>
1893
+ </div>
1894
+ </form>
1895
+ )}
1896
+ </div>
1897
+ )}
1898
+ </div>
1899
+ )}
1900
+
1901
+ {/* Section Configuration SMTP - Paramètres Système */}
1902
+ {mainSection === 'system' && (
1903
+ <div className="rounded-lg bg-white p-6 shadow-sm">
1904
+ <div className="flex items-center justify-between">
1905
+ <div>
1906
+ <h2 className="text-lg font-bold text-gray-900">Configuration SMTP</h2>
1907
+ <p className="mt-1 text-sm text-gray-600">
1908
+ Configurez votre serveur SMTP pour envoyer des emails avec votre email de
1909
+ société
1910
+ </p>
1911
+ </div>
1912
+ {smtpConfigured && (
1913
+ <div className="flex items-center gap-2 rounded-full bg-green-100 px-3 py-1.5">
1914
+ <svg
1915
+ className="h-4 w-4 text-green-600"
1916
+ fill="none"
1917
+ stroke="currentColor"
1918
+ viewBox="0 0 24 24"
1919
+ >
1920
+ <path
1921
+ strokeLinecap="round"
1922
+ strokeLinejoin="round"
1923
+ strokeWidth={2}
1924
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
1925
+ />
1926
+ </svg>
1927
+ <span className="text-xs font-medium text-green-800">Fonctionnel</span>
1928
+ </div>
1929
+ )}
1930
+ </div>
1931
+
1932
+ {smtpSuccess && (
1933
+ <div className="mt-4 rounded-lg border border-green-200 bg-green-50 p-4">
1934
+ <div className="flex items-center">
1935
+ <svg
1936
+ className="mr-3 h-5 w-5 text-green-600"
1937
+ fill="none"
1938
+ stroke="currentColor"
1939
+ viewBox="0 0 24 24"
1940
+ >
1941
+ <path
1942
+ strokeLinecap="round"
1943
+ strokeLinejoin="round"
1944
+ strokeWidth={2}
1945
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
1946
+ />
1947
+ </svg>
1948
+ <p className="text-sm font-medium text-green-800">{smtpSuccess}</p>
1949
+ <button
1950
+ onClick={() => setSmtpSuccess('')}
1951
+ className="ml-auto cursor-pointer text-green-600 hover:text-green-800"
1952
+ >
1953
+ <svg
1954
+ className="h-5 w-5"
1955
+ fill="none"
1956
+ stroke="currentColor"
1957
+ viewBox="0 0 24 24"
1958
+ >
1959
+ <path
1960
+ strokeLinecap="round"
1961
+ strokeLinejoin="round"
1962
+ strokeWidth={2}
1963
+ d="M6 18L18 6M6 6l12 12"
1964
+ />
1965
+ </svg>
1966
+ </button>
1967
+ </div>
1968
+ </div>
1969
+ )}
1970
+
1971
+ {smtpError && (
1972
+ <div className="mt-4 rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
1973
+ {smtpError}
1974
+ </div>
1975
+ )}
1976
+
1977
+ {smtpTestResult && (
1978
+ <div
1979
+ className={cn(
1980
+ 'mt-4 rounded-lg p-4',
1981
+ smtpTestResult.success
1982
+ ? 'border border-green-200 bg-green-50'
1983
+ : 'border border-indigo-200 bg-indigo-50',
1984
+ )}
1985
+ >
1986
+ <div className="flex items-center">
1987
+ {smtpTestResult.success ? (
1988
+ <svg
1989
+ className="mr-3 h-5 w-5 text-green-600"
1990
+ fill="none"
1991
+ stroke="currentColor"
1992
+ viewBox="0 0 24 24"
1993
+ >
1994
+ <path
1995
+ strokeLinecap="round"
1996
+ strokeLinejoin="round"
1997
+ strokeWidth={2}
1998
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
1999
+ />
2000
+ </svg>
2001
+ ) : (
2002
+ <svg
2003
+ className="mr-3 h-5 w-5 text-indigo-600"
2004
+ fill="none"
2005
+ stroke="currentColor"
2006
+ viewBox="0 0 24 24"
2007
+ >
2008
+ <path
2009
+ strokeLinecap="round"
2010
+ strokeLinejoin="round"
2011
+ strokeWidth={2}
2012
+ d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
2013
+ />
2014
+ </svg>
2015
+ )}
2016
+ <p
2017
+ className={cn(
2018
+ 'text-sm font-medium',
2019
+ smtpTestResult.success ? 'text-green-800' : 'text-indigo-800',
2020
+ )}
2021
+ >
2022
+ {smtpTestResult.message}
2023
+ </p>
2024
+ <button
2025
+ onClick={() => setSmtpTestResult(null)}
2026
+ className="ml-auto cursor-pointer text-gray-600 hover:text-gray-800"
2027
+ >
2028
+ <svg
2029
+ className="h-5 w-5"
2030
+ fill="none"
2031
+ stroke="currentColor"
2032
+ viewBox="0 0 24 24"
2033
+ >
2034
+ <path
2035
+ strokeLinecap="round"
2036
+ strokeLinejoin="round"
2037
+ strokeWidth={2}
2038
+ d="M6 18L18 6M6 6l12 12"
2039
+ />
2040
+ </svg>
2041
+ </button>
2042
+ </div>
2043
+ </div>
2044
+ )}
2045
+
2046
+ {smtpLoading ? (
2047
+ <div className="mt-6 text-center text-gray-500">Chargement...</div>
2048
+ ) : (
2049
+ <form onSubmit={handleSmtpSubmit} className="mt-6 space-y-4">
2050
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
2051
+ <div>
2052
+ <label className="block text-sm font-medium text-gray-700">
2053
+ Serveur SMTP (Host) *
2054
+ </label>
2055
+ <input
2056
+ type="text"
2057
+ required
2058
+ value={smtpData.host}
2059
+ onChange={(e) => setSmtpData({ ...smtpData, host: e.target.value })}
2060
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2061
+ placeholder="smtp.gmail.com"
2062
+ />
2063
+ </div>
2064
+
2065
+ <div>
2066
+ <label className="block text-sm font-medium text-gray-700">Port *</label>
2067
+ <input
2068
+ type="number"
2069
+ required
2070
+ min="1"
2071
+ max="65535"
2072
+ value={smtpData.port}
2073
+ onChange={(e) => setSmtpData({ ...smtpData, port: e.target.value })}
2074
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2075
+ placeholder="587"
2076
+ />
2077
+ <p className="mt-1 text-xs text-gray-500">
2078
+ Ports courants : 587 (TLS), 465 (SSL), 25 (non sécurisé)
2079
+ </p>
2080
+ </div>
2081
+
2082
+ <div className="flex items-center">
2083
+ <input
2084
+ type="checkbox"
2085
+ id="smtp-secure"
2086
+ checked={smtpData.secure}
2087
+ onChange={(e) => setSmtpData({ ...smtpData, secure: e.target.checked })}
2088
+ className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
2089
+ />
2090
+ <label
2091
+ htmlFor="smtp-secure"
2092
+ className="ml-2 text-sm font-medium text-gray-700"
2093
+ >
2094
+ Connexion sécurisée (SSL/TLS)
2095
+ </label>
2096
+ </div>
2097
+
2098
+ <div className="md:col-span-2">
2099
+ <label className="block text-sm font-medium text-gray-700">
2100
+ Nom d'utilisateur *
2101
+ </label>
2102
+ <input
2103
+ type="text"
2104
+ required
2105
+ value={smtpData.username}
2106
+ onChange={(e) => setSmtpData({ ...smtpData, username: e.target.value })}
2107
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2108
+ placeholder="votre.email@exemple.com"
2109
+ />
2110
+ </div>
2111
+
2112
+ <div className="md:col-span-2">
2113
+ <label className="block text-sm font-medium text-gray-700">
2114
+ Mot de passe *
2115
+ </label>
2116
+ <div className="relative mt-1">
2117
+ <input
2118
+ type={showSmtpPassword ? 'text' : 'password'}
2119
+ required
2120
+ value={smtpData.password}
2121
+ onChange={(e) => setSmtpData({ ...smtpData, password: e.target.value })}
2122
+ className="block w-full rounded-lg border border-gray-300 px-4 py-2 pr-10 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2123
+ placeholder="••••••••"
2124
+ />
2125
+ <button
2126
+ type="button"
2127
+ onClick={() => setShowSmtpPassword((prev) => !prev)}
2128
+ className="absolute inset-y-0 right-0 flex cursor-pointer items-center pr-3 text-gray-400 hover:text-gray-600"
2129
+ aria-label={
2130
+ showSmtpPassword
2131
+ ? 'Masquer le mot de passe'
2132
+ : 'Afficher le mot de passe'
2133
+ }
2134
+ >
2135
+ {showSmtpPassword ? <EyeOff /> : <Eye />}
2136
+ </button>
2137
+ </div>
2138
+ <p className="mt-1 text-xs text-gray-500">
2139
+ Pour Gmail, utilisez un mot de passe d'application
2140
+ </p>
2141
+ </div>
2142
+
2143
+ <div>
2144
+ <label className="block text-sm font-medium text-gray-700">
2145
+ Email expéditeur (From) *
2146
+ </label>
2147
+ <input
2148
+ type="email"
2149
+ required
2150
+ value={smtpData.fromEmail}
2151
+ onChange={(e) => setSmtpData({ ...smtpData, fromEmail: e.target.value })}
2152
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2153
+ placeholder="votre.email@exemple.com"
2154
+ />
2155
+ </div>
2156
+
2157
+ <div>
2158
+ <label className="block text-sm font-medium text-gray-700">
2159
+ Nom expéditeur (optionnel)
2160
+ </label>
2161
+ <input
2162
+ type="text"
2163
+ value={smtpData.fromName}
2164
+ onChange={(e) => setSmtpData({ ...smtpData, fromName: e.target.value })}
2165
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2166
+ placeholder="Votre Nom"
2167
+ />
2168
+ </div>
2169
+ </div>
2170
+
2171
+ <div className="mt-4 rounded-lg bg-indigo-50 p-3 text-xs text-indigo-900">
2172
+ <p className="font-medium">Utiliser Gmail avec SMTP</p>
2173
+ <p className="mt-1">
2174
+ Si vous utilisez une adresse Gmail, vous devez créer un{' '}
2175
+ <span className="font-semibold">mot de passe d&apos;application</span> dédié
2176
+ et le renseigner dans le champ &quot;Mot de passe&quot; ci-dessus.
2177
+ Rendez-vous sur{' '}
2178
+ <Link
2179
+ href="https://myaccount.google.com/apppasswords"
2180
+ target="_blank"
2181
+ rel="noreferrer"
2182
+ className="font-semibold underline"
2183
+ >
2184
+ la page des mots de passe d&apos;application Google
2185
+ </Link>{' '}
2186
+ pour en générer un (compte Google protégé par la validation en deux étapes
2187
+ requis).
2188
+ </p>
2189
+ </div>
2190
+
2191
+ <div className="mt-6 rounded-lg border border-dashed border-gray-200 bg-gray-50 p-4">
2192
+ <label className="mb-2 block text-sm font-medium text-gray-700">
2193
+ Votre signature (optionnel)
2194
+ </label>
2195
+ <p className="mb-2 text-xs text-gray-500">
2196
+ Cette signature sera ajoutée à la fin de tous les emails envoyés avec cette
2197
+ configuration SMTP (par exemple&nbsp;: nom, fonction, coordonnées, mentions
2198
+ légales…).
2199
+ </p>
2200
+ <div className="mt-1">
2201
+ <Editor
2202
+ ref={smtpSignatureEditorRef}
2203
+ onReady={(methods) => {
2204
+ // garder la ref synchronisée
2205
+ smtpSignatureEditorRef.current = methods;
2206
+ // injecter la signature existante dès que l’éditeur est prêt
2207
+ if (smtpData.signature) {
2208
+ methods.injectHTML(smtpData.signature);
2209
+ }
2210
+ }}
2211
+ />
2212
+ </div>
2213
+ </div>
2214
+
2215
+ <div className="flex flex-col gap-3 pt-4 sm:flex-row sm:justify-end">
2216
+ <button
2217
+ type="button"
2218
+ onClick={handleSmtpTest}
2219
+ disabled={smtpTesting || smtpSaving}
2220
+ className="w-full cursor-pointer rounded-lg border border-indigo-600 px-6 py-2 text-sm font-medium text-indigo-600 transition-colors hover:bg-indigo-50 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
2221
+ >
2222
+ {smtpTesting ? 'Test en cours...' : 'Tester la connexion'}
2223
+ </button>
2224
+ <button
2225
+ type="submit"
2226
+ disabled={smtpSaving || smtpTesting}
2227
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
2228
+ >
2229
+ {smtpSaving ? 'Enregistrement...' : 'Enregistrer'}
2230
+ </button>
2231
+ </div>
2232
+ </form>
2233
+ )}
2234
+ </div>
2235
+ )}
2236
+
2237
+ {/* Section Gestion des statuts - Paramètres de l'Application */}
2238
+ {mainSection === 'app' && (
2239
+ <>
2240
+ {isAdmin ? (
2241
+ <div className="space-y-6">
2242
+ <div className="rounded-lg bg-white p-6 shadow-sm">
2243
+ <div className="flex items-center justify-between">
2244
+ <div>
2245
+ <h2 className="text-lg font-bold text-gray-900">Gestion des statuts</h2>
2246
+ <p className="mt-1 text-sm text-gray-600">
2247
+ Gérez les statuts pour catégoriser vos contacts
2248
+ </p>
2249
+ </div>
2250
+ {!showStatusForm && (
2251
+ <button
2252
+ onClick={() => {
2253
+ setShowStatusForm(true);
2254
+ setEditingStatus(null);
2255
+ setStatusFormData({ name: '', color: '#3B82F6' });
2256
+ setStatusError('');
2257
+ setStatusSuccess('');
2258
+ }}
2259
+ className="cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700"
2260
+ >
2261
+ + Ajouter un statut
2262
+ </button>
2263
+ )}
2264
+ </div>
2265
+
2266
+ {statusSuccess && (
2267
+ <div className="mt-4 rounded-lg border border-green-200 bg-green-50 p-4">
2268
+ <div className="flex items-center">
2269
+ <svg
2270
+ className="mr-3 h-5 w-5 text-green-600"
2271
+ fill="none"
2272
+ stroke="currentColor"
2273
+ viewBox="0 0 24 24"
2274
+ >
2275
+ <path
2276
+ strokeLinecap="round"
2277
+ strokeLinejoin="round"
2278
+ strokeWidth={2}
2279
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
2280
+ />
2281
+ </svg>
2282
+ <p className="text-sm font-medium text-green-800">{statusSuccess}</p>
2283
+ <button
2284
+ onClick={() => setStatusSuccess('')}
2285
+ className="ml-auto cursor-pointer text-green-600 hover:text-green-800"
2286
+ >
2287
+ <svg
2288
+ className="h-5 w-5"
2289
+ fill="none"
2290
+ stroke="currentColor"
2291
+ viewBox="0 0 24 24"
2292
+ >
2293
+ <path
2294
+ strokeLinecap="round"
2295
+ strokeLinejoin="round"
2296
+ strokeWidth={2}
2297
+ d="M6 18L18 6M6 6l12 12"
2298
+ />
2299
+ </svg>
2300
+ </button>
2301
+ </div>
2302
+ </div>
2303
+ )}
2304
+
2305
+ {statusError && (
2306
+ <div className="mt-4 rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
2307
+ {statusError}
2308
+ </div>
2309
+ )}
2310
+
2311
+ {showStatusForm ? (
2312
+ <form onSubmit={handleStatusSubmit} className="mt-6 space-y-4">
2313
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
2314
+ <div>
2315
+ <label className="block text-sm font-medium text-gray-700">
2316
+ Nom du statut *
2317
+ </label>
2318
+ <input
2319
+ type="text"
2320
+ required
2321
+ value={statusFormData.name}
2322
+ onChange={(e) =>
2323
+ setStatusFormData({ ...statusFormData, name: e.target.value })
2324
+ }
2325
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2326
+ placeholder="Ex: Nouveau"
2327
+ />
2328
+ </div>
2329
+
2330
+ <div>
2331
+ <label className="block text-sm font-medium text-gray-700">
2332
+ Couleur *
2333
+ </label>
2334
+ <div className="mt-1 flex items-center gap-3">
2335
+ <input
2336
+ type="color"
2337
+ value={statusFormData.color}
2338
+ onChange={(e) =>
2339
+ setStatusFormData({ ...statusFormData, color: e.target.value })
2340
+ }
2341
+ className="h-10 w-20 cursor-pointer rounded-lg border border-gray-300"
2342
+ />
2343
+ <input
2344
+ type="text"
2345
+ value={statusFormData.color}
2346
+ onChange={(e) =>
2347
+ setStatusFormData({ ...statusFormData, color: e.target.value })
2348
+ }
2349
+ className="block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2350
+ placeholder="#3B82F6"
2351
+ />
2352
+ </div>
2353
+ </div>
2354
+ </div>
2355
+
2356
+ <div className="flex flex-col gap-3 pt-4 sm:flex-row sm:justify-end">
2357
+ <button
2358
+ type="button"
2359
+ onClick={() => {
2360
+ setShowStatusForm(false);
2361
+ setEditingStatus(null);
2362
+ setStatusFormData({ name: '', color: '#3B82F6' });
2363
+ setStatusError('');
2364
+ setStatusSuccess('');
2365
+ }}
2366
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
2367
+ >
2368
+ Annuler
2369
+ </button>
2370
+ <button
2371
+ type="submit"
2372
+ disabled={statusSaving}
2373
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
2374
+ >
2375
+ {statusSaving
2376
+ ? 'Enregistrement...'
2377
+ : editingStatus
2378
+ ? 'Modifier'
2379
+ : 'Créer'}
2380
+ </button>
2381
+ </div>
2382
+ </form>
2383
+ ) : (
2384
+ <div className="mt-6">
2385
+ {statusesLoading ? (
2386
+ <div className="text-center text-gray-500">Chargement...</div>
2387
+ ) : statuses.length === 0 ? (
2388
+ <div className="text-center text-gray-500">
2389
+ Aucun statut. Cliquez sur &quot;Ajouter un statut&quot; pour en créer
2390
+ un.
2391
+ </div>
2392
+ ) : (
2393
+ <div className="space-y-2">
2394
+ {statuses.map((status) => (
2395
+ <div
2396
+ key={status.id}
2397
+ className="flex items-center justify-between rounded-lg border border-gray-200 p-4"
2398
+ >
2399
+ <div className="flex items-center gap-3">
2400
+ <div
2401
+ className="h-6 w-6 rounded-full"
2402
+ style={{ backgroundColor: status.color }}
2403
+ />
2404
+ <span className="font-medium text-gray-900">{status.name}</span>
2405
+ </div>
2406
+ <div className="flex items-center gap-2">
2407
+ <button
2408
+ onClick={() => handleStatusEdit(status)}
2409
+ className="cursor-pointer rounded-lg border border-gray-300 px-3 py-1.5 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50"
2410
+ >
2411
+ Modifier
2412
+ </button>
2413
+ <button
2414
+ onClick={() => handleStatusDelete(status.id)}
2415
+ className="cursor-pointer rounded-lg border border-indigo-300 px-3 py-1.5 text-sm font-medium text-indigo-700 transition-colors hover:bg-indigo-50"
2416
+ >
2417
+ Supprimer
2418
+ </button>
2419
+ </div>
2420
+ </div>
2421
+ ))}
2422
+ </div>
2423
+ )}
2424
+ </div>
2425
+ )}
2426
+ </div>
2427
+
2428
+ {/* Section Motifs de fermeture - Paramètres de l'Application */}
2429
+ <div className="rounded-lg bg-white p-6 shadow-sm">
2430
+ <div className="flex items-center justify-between gap-2">
2431
+ <div>
2432
+ <h2 className="text-lg font-bold text-gray-900">Motifs de fermeture</h2>
2433
+ <p className="mt-1 text-sm text-gray-600">
2434
+ Gérez la liste des motifs de fermeture utilisés lorsque les contacts
2435
+ passent au statut &quot;Fermé&quot;.
2436
+ </p>
2437
+ </div>
2438
+ {!showClosingReasonForm && (
2439
+ <button
2440
+ onClick={() => {
2441
+ setShowClosingReasonForm(true);
2442
+ setClosingReasonFormData({ id: null, name: '' });
2443
+ setClosingReasonError('');
2444
+ setClosingReasonSuccess('');
2445
+ }}
2446
+ className="cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-nowrap text-white transition-colors hover:bg-indigo-700"
2447
+ >
2448
+ + Ajouter un motif
2449
+ </button>
2450
+ )}
2451
+ </div>
2452
+
2453
+ {closingReasonSuccess && (
2454
+ <div className="mt-4 rounded-lg border border-green-200 bg-green-50 p-4">
2455
+ <div className="flex items-center">
2456
+ <svg
2457
+ className="mr-3 h-5 w-5 text-green-600"
2458
+ fill="none"
2459
+ stroke="currentColor"
2460
+ viewBox="0 0 24 24"
2461
+ >
2462
+ <path
2463
+ strokeLinecap="round"
2464
+ strokeLinejoin="round"
2465
+ strokeWidth={2}
2466
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
2467
+ />
2468
+ </svg>
2469
+ <p className="text-sm font-medium text-green-800">
2470
+ {closingReasonSuccess}
2471
+ </p>
2472
+ <button
2473
+ onClick={() => setClosingReasonSuccess('')}
2474
+ className="ml-auto cursor-pointer text-green-600 hover:text-green-800"
2475
+ >
2476
+ <svg
2477
+ className="h-5 w-5"
2478
+ fill="none"
2479
+ stroke="currentColor"
2480
+ viewBox="0 0 24 24"
2481
+ >
2482
+ <path
2483
+ strokeLinecap="round"
2484
+ strokeLinejoin="round"
2485
+ strokeWidth={2}
2486
+ d="M6 18L18 6M6 6l12 12"
2487
+ />
2488
+ </svg>
2489
+ </button>
2490
+ </div>
2491
+ </div>
2492
+ )}
2493
+
2494
+ {closingReasonError && (
2495
+ <div className="mt-4 rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
2496
+ {closingReasonError}
2497
+ </div>
2498
+ )}
2499
+
2500
+ {/* Formulaire d'ajout / édition */}
2501
+ {showClosingReasonForm && (
2502
+ <form
2503
+ className="mt-6 flex flex-col sm:flex-row sm:items-center sm:justify-between"
2504
+ onSubmit={async (e) => {
2505
+ e.preventDefault();
2506
+ setClosingReasonError('');
2507
+ setClosingReasonSuccess('');
2508
+ try {
2509
+ const isEdit = !!closingReasonFormData.id;
2510
+ const url = isEdit
2511
+ ? `/api/settings/closing-reasons/${closingReasonFormData.id}`
2512
+ : '/api/settings/closing-reasons';
2513
+ const method = isEdit ? 'PUT' : 'POST';
2514
+
2515
+ const response = await fetch(url, {
2516
+ method,
2517
+ headers: { 'Content-Type': 'application/json' },
2518
+ body: JSON.stringify({ name: closingReasonFormData.name }),
2519
+ });
2520
+
2521
+ const data = await response.json();
2522
+ if (!response.ok) {
2523
+ throw new Error(
2524
+ data.error || 'Erreur lors de la sauvegarde du motif',
2525
+ );
2526
+ }
2527
+
2528
+ setClosingReasonSuccess(
2529
+ isEdit
2530
+ ? 'Motif de fermeture mis à jour avec succès.'
2531
+ : 'Motif de fermeture créé avec succès.',
2532
+ );
2533
+ setClosingReasonFormData({ id: null, name: '' });
2534
+ setShowClosingReasonForm(false);
2535
+
2536
+ // Recharger la liste
2537
+ const res = await fetch('/api/settings/closing-reasons');
2538
+ if (res.ok) {
2539
+ const reasonsData = await res.json();
2540
+ setClosingReasons(Array.isArray(reasonsData) ? reasonsData : []);
2541
+ }
2542
+ } catch (error: any) {
2543
+ console.error('Erreur motif de fermeture:', error);
2544
+ setClosingReasonError(
2545
+ error.message ||
2546
+ 'Erreur lors de la sauvegarde du motif de fermeture',
2547
+ );
2548
+ }
2549
+ }}
2550
+ >
2551
+ <div className="flex gap-4">
2552
+ <div className="w-full">
2553
+ <label className="block text-sm font-medium text-gray-700">
2554
+ Nom du motif *
2555
+ </label>
2556
+ <input
2557
+ type="text"
2558
+ required
2559
+ value={closingReasonFormData.name}
2560
+ onChange={(e) =>
2561
+ setClosingReasonFormData((prev) => ({
2562
+ ...prev,
2563
+ name: e.target.value,
2564
+ }))
2565
+ }
2566
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
2567
+ placeholder="Ex: Faux numéro"
2568
+ />
2569
+ </div>
2570
+ </div>
2571
+
2572
+ <div className="flex h-fit flex-col gap-3 pt-4 sm:flex-row sm:justify-end">
2573
+ <button
2574
+ type="button"
2575
+ onClick={() => {
2576
+ setShowClosingReasonForm(false);
2577
+ setClosingReasonFormData({ id: null, name: '' });
2578
+ setClosingReasonError('');
2579
+ setClosingReasonSuccess('');
2580
+ }}
2581
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
2582
+ >
2583
+ Annuler
2584
+ </button>
2585
+ <button
2586
+ type="submit"
2587
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
2588
+ >
2589
+ {closingReasonFormData.id ? 'Mettre à jour' : 'Créer'}
2590
+ </button>
2591
+ </div>
2592
+ </form>
2593
+ )}
2594
+
2595
+ {/* Liste des motifs existants */}
2596
+ <div className="mt-6">
2597
+ {closingReasonsLoading ? (
2598
+ <div className="text-center text-gray-500">Chargement...</div>
2599
+ ) : closingReasons.length === 0 ? (
2600
+ <div className="text-center text-gray-500">
2601
+ Aucun motif. Ajoutez un motif pour commencer.
2602
+ </div>
2603
+ ) : (
2604
+ <div className="space-y-2">
2605
+ {closingReasons.map((reason) => (
2606
+ <div
2607
+ key={reason.id}
2608
+ className="flex items-center justify-between rounded-lg border border-gray-200 p-4"
2609
+ >
2610
+ <div className="flex items-center gap-3">
2611
+ <div className="h-6 w-6 rounded-full bg-gray-600" />
2612
+ <span className="font-medium text-gray-900">{reason.name}</span>
2613
+ </div>
2614
+ <div className="flex items-center gap-2">
2615
+ <button
2616
+ type="button"
2617
+ onClick={() => {
2618
+ setClosingReasonFormData({
2619
+ id: reason.id,
2620
+ name: reason.name,
2621
+ });
2622
+ setClosingReasonError('');
2623
+ setClosingReasonSuccess('');
2624
+ setShowClosingReasonForm(true);
2625
+ }}
2626
+ className="cursor-pointer rounded-lg border border-gray-300 px-3 py-1.5 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50"
2627
+ >
2628
+ Modifier
2629
+ </button>
2630
+ <button
2631
+ type="button"
2632
+ onClick={async () => {
2633
+ if (
2634
+ !confirm(
2635
+ `Supprimer le motif "${reason.name}" ? Cela n'affectera pas les contacts existants.`,
2636
+ )
2637
+ ) {
2638
+ return;
2639
+ }
2640
+ try {
2641
+ setClosingReasonError('');
2642
+ setClosingReasonSuccess('');
2643
+ const res = await fetch(
2644
+ `/api/settings/closing-reasons/${reason.id}`,
2645
+ {
2646
+ method: 'DELETE',
2647
+ },
2648
+ );
2649
+ const data = await res.json();
2650
+ if (!res.ok) {
2651
+ throw new Error(
2652
+ data.error || 'Erreur lors de la suppression du motif',
2653
+ );
2654
+ }
2655
+ setClosingReasonSuccess(
2656
+ 'Motif de fermeture supprimé avec succès.',
2657
+ );
2658
+ setClosingReasons((prev) =>
2659
+ prev.filter((r) => r.id !== reason.id),
2660
+ );
2661
+ } catch (error: any) {
2662
+ console.error('Erreur suppression motif:', error);
2663
+ setClosingReasonError(
2664
+ error.message || 'Erreur lors de la suppression du motif',
2665
+ );
2666
+ }
2667
+ }}
2668
+ className="cursor-pointer rounded-lg border border-indigo-300 px-3 py-1.5 text-sm font-medium text-indigo-700 transition-colors hover:bg-indigo-50"
2669
+ >
2670
+ Supprimer
2671
+ </button>
2672
+ </div>
2673
+ </div>
2674
+ ))}
2675
+ </div>
2676
+ )}
2677
+ </div>
2678
+ </div>
2679
+ </div>
2680
+ ) : (
2681
+ <div className="rounded-lg bg-white p-6 shadow-sm">
2682
+ <div className="py-12 text-center">
2683
+ <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-indigo-100">
2684
+ <Grid3x3 className="h-6 w-6 text-indigo-600" />
2685
+ </div>
2686
+ <h3 className="mt-4 text-lg font-semibold text-gray-900">Accès restreint</h3>
2687
+ <p className="mt-2 text-sm text-gray-600">
2688
+ Cette section est réservée aux administrateurs.
2689
+ </p>
2690
+ </div>
2691
+ </div>
2692
+ )}
2693
+ </>
2694
+ )}
2695
+
2696
+ {/* Section Intégrations */}
2697
+ {mainSection === 'integrations' && (
2698
+ <>
2699
+ {isAdmin ? (
2700
+ <div className="space-y-6">
2701
+ {/* Google */}
2702
+ <div className="rounded-lg bg-white p-6 shadow-sm">
2703
+ <div className="flex items-center justify-between">
2704
+ <div>
2705
+ <h2 className="text-lg font-bold text-gray-900">Intégration Google</h2>
2706
+ <p className="mt-1 text-sm text-gray-600">
2707
+ Connectez votre compte Google pour utiliser Google Calendar et Google
2708
+ Drive.
2709
+ </p>
2710
+ </div>
2711
+ </div>
2712
+
2713
+ <div className="mt-6">
2714
+ <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
2715
+ <div className="flex items-center justify-between">
2716
+ <div className="flex items-center gap-3">
2717
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100">
2718
+ <Grid3x3 className="h-6 w-6 text-blue-600" />
2719
+ </div>
2720
+ <div>
2721
+ <h3 className="font-medium text-gray-900">
2722
+ Google Calendar & Drive
2723
+ </h3>
2724
+ <p className="mt-1 text-xs text-gray-500">
2725
+ Accédez à votre calendrier et à vos fichiers Google Drive
2726
+ </p>
2727
+ </div>
2728
+ </div>
2729
+ </div>
2730
+ {googleLoading ? (
2731
+ <div className="mt-4 text-center text-sm text-gray-500">
2732
+ Chargement...
2733
+ </div>
2734
+ ) : (
2735
+ <div className="mt-4 flex items-center justify-between">
2736
+ {googleAccount?.connected ? (
2737
+ <>
2738
+ <div className="flex items-center gap-2">
2739
+ <span className="rounded-full bg-green-100 px-3 py-1 text-xs font-medium text-green-700">
2740
+ Connecté
2741
+ </span>
2742
+ {googleAccount.email && (
2743
+ <span className="text-xs text-gray-600">
2744
+ {googleAccount.email}
2745
+ </span>
2746
+ )}
2747
+ </div>
2748
+ <button
2749
+ type="button"
2750
+ onClick={handleGoogleDisconnect}
2751
+ disabled={googleDisconnecting}
2752
+ className="cursor-pointer rounded-lg border border-indigo-300 bg-white px-3 py-1.5 text-xs font-medium text-indigo-700 transition-colors hover:bg-indigo-50 disabled:cursor-not-allowed disabled:opacity-50"
2753
+ >
2754
+ {googleDisconnecting ? 'Déconnexion...' : 'Déconnecter'}
2755
+ </button>
2756
+ </>
2757
+ ) : (
2758
+ <>
2759
+ <span className="rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-700">
2760
+ Non connecté
2761
+ </span>
2762
+ <button
2763
+ type="button"
2764
+ onClick={handleGoogleConnect}
2765
+ className="cursor-pointer rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-indigo-700"
2766
+ >
2767
+ Connecter
2768
+ </button>
2769
+ </>
2770
+ )}
2771
+ </div>
2772
+ )}
2773
+ </div>
2774
+ </div>
2775
+ </div>
2776
+
2777
+ {/* Google Sheets */}
2778
+ <div className="rounded-lg bg-white p-6 shadow-sm">
2779
+ <div className="flex items-center justify-between">
2780
+ <div>
2781
+ <h2 className="text-lg font-bold text-gray-900">
2782
+ Intégration Google Sheets
2783
+ </h2>
2784
+ <p className="mt-1 text-sm text-gray-600">
2785
+ Importez automatiquement des contacts à partir d&apos;un Google Sheet.
2786
+ </p>
2787
+ </div>
2788
+ <div className="flex items-center gap-2">
2789
+ <button
2790
+ type="button"
2791
+ onClick={handleGoogleSheetSync}
2792
+ disabled={googleSheetSyncing}
2793
+ className="cursor-pointer rounded-lg border border-indigo-600 px-4 py-2 text-sm font-medium text-indigo-600 transition-colors hover:bg-indigo-50 disabled:cursor-not-allowed disabled:opacity-50"
2794
+ >
2795
+ {googleSheetSyncing ? 'Synchronisation...' : 'Synchroniser'}
2796
+ </button>
2797
+ <button
2798
+ onClick={() => {
2799
+ setEditingGoogleSheetConfig(null);
2800
+ setGoogleSheetFormData({
2801
+ name: '',
2802
+ active: true,
2803
+ sheetUrl: '',
2804
+ sheetName: '',
2805
+ headerRow: '1',
2806
+ defaultStatusId: null,
2807
+ defaultAssignedUserId: null,
2808
+ });
2809
+ setGoogleSheetMappings([]);
2810
+ setGoogleSheetStep(1);
2811
+ setShowGoogleSheetModal(true);
2812
+ setGoogleSheetError('');
2813
+ setGoogleSheetSuccess('');
2814
+ }}
2815
+ className="cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700"
2816
+ >
2817
+ + Ajouter
2818
+ </button>
2819
+ </div>
2820
+ </div>
2821
+
2822
+ {googleSheetSuccess && (
2823
+ <div className="mt-4 rounded-lg border border-green-200 bg-green-50 p-4">
2824
+ <div className="flex items-center">
2825
+ <svg
2826
+ className="mr-3 h-5 w-5 text-green-600"
2827
+ fill="none"
2828
+ stroke="currentColor"
2829
+ viewBox="0 0 24 24"
2830
+ >
2831
+ <path
2832
+ strokeLinecap="round"
2833
+ strokeLinejoin="round"
2834
+ strokeWidth={2}
2835
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
2836
+ />
2837
+ </svg>
2838
+ <p className="text-sm font-medium text-green-800">
2839
+ {googleSheetSuccess}
2840
+ </p>
2841
+ <button
2842
+ onClick={() => setGoogleSheetSuccess('')}
2843
+ className="ml-auto cursor-pointer text-green-600 hover:text-green-800"
2844
+ >
2845
+ <svg
2846
+ className="h-5 w-5"
2847
+ fill="none"
2848
+ stroke="currentColor"
2849
+ viewBox="0 0 24 24"
2850
+ >
2851
+ <path
2852
+ strokeLinecap="round"
2853
+ strokeLinejoin="round"
2854
+ strokeWidth={2}
2855
+ d="M6 18L18 6M6 6l12 12"
2856
+ />
2857
+ </svg>
2858
+ </button>
2859
+ </div>
2860
+ </div>
2861
+ )}
2862
+
2863
+ {googleSheetError && (
2864
+ <div className="mt-4 rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
2865
+ {googleSheetError}
2866
+ </div>
2867
+ )}
2868
+
2869
+ {googleSheetLoading ? (
2870
+ <div className="mt-6 text-center text-gray-500">Chargement...</div>
2871
+ ) : googleSheetConfigs.length === 0 ? (
2872
+ <div className="mt-6 rounded-lg border border-dashed border-gray-300 bg-gray-50 p-8 text-center">
2873
+ <p className="text-sm text-gray-600">
2874
+ Aucune configuration Google Sheets
2875
+ </p>
2876
+ <p className="mt-1 text-xs text-gray-500">
2877
+ Cliquez sur &quot;+ Ajouter&quot; pour créer votre première
2878
+ configuration
2879
+ </p>
2880
+ </div>
2881
+ ) : (
2882
+ <div className="mt-6 space-y-3">
2883
+ {googleSheetConfigs.map((config) => (
2884
+ <div
2885
+ key={config.id}
2886
+ className="flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 p-4"
2887
+ >
2888
+ <div className="flex-1">
2889
+ <div className="flex items-center gap-2">
2890
+ <h3 className="font-medium text-gray-900">{config.name}</h3>
2891
+ {config.active ? (
2892
+ <span className="rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
2893
+ Actif
2894
+ </span>
2895
+ ) : (
2896
+ <span className="rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-800">
2897
+ Inactif
2898
+ </span>
2899
+ )}
2900
+ </div>
2901
+ <p className="mt-1 text-xs text-gray-500">
2902
+ {config.sheetName} - Ligne {config.headerRow}
2903
+ </p>
2904
+ </div>
2905
+ <div className="flex items-center gap-2">
2906
+ <button
2907
+ onClick={() => handleEditGoogleSheet(config)}
2908
+ className="cursor-pointer rounded-lg border border-gray-300 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100"
2909
+ >
2910
+ Modifier
2911
+ </button>
2912
+ <button
2913
+ onClick={() => handleDeleteGoogleSheet(config.id)}
2914
+ className="cursor-pointer rounded-lg border border-indigo-300 px-3 py-1.5 text-xs font-medium text-indigo-700 transition-colors hover:bg-indigo-50"
2915
+ >
2916
+ Supprimer
2917
+ </button>
2918
+ </div>
2919
+ </div>
2920
+ ))}
2921
+ </div>
2922
+ )}
2923
+ </div>
2924
+
2925
+ {/* Meta Lead Ads */}
2926
+ <div className="rounded-lg bg-white p-6 shadow-sm">
2927
+ <div className="flex items-center justify-between">
2928
+ <div>
2929
+ <h2 className="text-lg font-bold text-gray-900">
2930
+ Intégration Meta Lead Ads
2931
+ </h2>
2932
+ <p className="mt-1 text-sm text-gray-600">
2933
+ Recevez automatiquement les leads depuis Facebook Lead Ads.
2934
+ </p>
2935
+ </div>
2936
+ <button
2937
+ onClick={() => {
2938
+ setEditingMetaLeadConfig(null);
2939
+ setMetaLeadFormData({
2940
+ name: '',
2941
+ active: true,
2942
+ pageId: '',
2943
+ accessToken: '',
2944
+ verifyToken: '',
2945
+ defaultStatusId: null,
2946
+ defaultAssignedUserId: null,
2947
+ });
2948
+ setShowMetaLeadModal(true);
2949
+ setMetaLeadError('');
2950
+ setMetaLeadSuccess('');
2951
+ }}
2952
+ className="cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700"
2953
+ >
2954
+ + Ajouter
2955
+ </button>
2956
+ </div>
2957
+
2958
+ {metaLeadSuccess && (
2959
+ <div className="mt-4 rounded-lg border border-green-200 bg-green-50 p-4">
2960
+ <div className="flex items-center">
2961
+ <svg
2962
+ className="mr-3 h-5 w-5 text-green-600"
2963
+ fill="none"
2964
+ stroke="currentColor"
2965
+ viewBox="0 0 24 24"
2966
+ >
2967
+ <path
2968
+ strokeLinecap="round"
2969
+ strokeLinejoin="round"
2970
+ strokeWidth={2}
2971
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
2972
+ />
2973
+ </svg>
2974
+ <p className="text-sm font-medium text-green-800">{metaLeadSuccess}</p>
2975
+ <button
2976
+ onClick={() => setMetaLeadSuccess('')}
2977
+ className="ml-auto cursor-pointer text-green-600 hover:text-green-800"
2978
+ >
2979
+ <svg
2980
+ className="h-5 w-5"
2981
+ fill="none"
2982
+ stroke="currentColor"
2983
+ viewBox="0 0 24 24"
2984
+ >
2985
+ <path
2986
+ strokeLinecap="round"
2987
+ strokeLinejoin="round"
2988
+ strokeWidth={2}
2989
+ d="M6 18L18 6M6 6l12 12"
2990
+ />
2991
+ </svg>
2992
+ </button>
2993
+ </div>
2994
+ </div>
2995
+ )}
2996
+
2997
+ {metaLeadError && (
2998
+ <div className="mt-4 rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
2999
+ {metaLeadError}
3000
+ </div>
3001
+ )}
3002
+
3003
+ {metaLeadLoading ? (
3004
+ <div className="mt-6 text-center text-gray-500">Chargement...</div>
3005
+ ) : metaLeadConfigs.length === 0 ? (
3006
+ <div className="mt-6 rounded-lg border border-dashed border-gray-300 bg-gray-50 p-8 text-center">
3007
+ <p className="text-sm text-gray-600">
3008
+ Aucune configuration Meta Lead Ads
3009
+ </p>
3010
+ <p className="mt-1 text-xs text-gray-500">
3011
+ Cliquez sur &quot;+ Ajouter&quot; pour créer votre première
3012
+ configuration
3013
+ </p>
3014
+ </div>
3015
+ ) : (
3016
+ <div className="mt-6 space-y-3">
3017
+ {metaLeadConfigs.map((config) => (
3018
+ <div
3019
+ key={config.id}
3020
+ className="flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 p-4"
3021
+ >
3022
+ <div className="flex-1">
3023
+ <div className="flex items-center gap-2">
3024
+ <h3 className="font-medium text-gray-900">{config.name}</h3>
3025
+ {config.active ? (
3026
+ <span className="rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
3027
+ Actif
3028
+ </span>
3029
+ ) : (
3030
+ <span className="rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-800">
3031
+ Inactif
3032
+ </span>
3033
+ )}
3034
+ </div>
3035
+ <p className="mt-1 text-xs text-gray-500">
3036
+ Page ID: {config.pageId}
3037
+ </p>
3038
+ </div>
3039
+ <div className="flex items-center gap-2">
3040
+ <button
3041
+ onClick={() => handleEditMetaLead(config)}
3042
+ className="cursor-pointer rounded-lg border border-gray-300 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100"
3043
+ >
3044
+ Modifier
3045
+ </button>
3046
+ <button
3047
+ onClick={() => handleDeleteMetaLead(config.id)}
3048
+ className="cursor-pointer rounded-lg border border-indigo-300 px-3 py-1.5 text-xs font-medium text-indigo-700 transition-colors hover:bg-indigo-50"
3049
+ >
3050
+ Supprimer
3051
+ </button>
3052
+ </div>
3053
+ </div>
3054
+ ))}
3055
+ </div>
3056
+ )}
3057
+ </div>
3058
+
3059
+ {/* Google Ads */}
3060
+ <div className="rounded-lg bg-white p-6 shadow-sm">
3061
+ <div className="flex items-center justify-between">
3062
+ <div>
3063
+ <h2 className="text-lg font-bold text-gray-900">
3064
+ Intégration Google Ads
3065
+ </h2>
3066
+ <p className="mt-1 text-sm text-gray-600">
3067
+ Recevez automatiquement les leads depuis Google Ads Lead Forms.
3068
+ </p>
3069
+ </div>
3070
+ <button
3071
+ onClick={() => {
3072
+ setEditingGoogleAdsConfig(null);
3073
+ setGoogleAdsFormData({
3074
+ name: '',
3075
+ active: true,
3076
+ webhookKey: '',
3077
+ defaultStatusId: null,
3078
+ defaultAssignedUserId: null,
3079
+ });
3080
+ setShowGoogleAdsModal(true);
3081
+ setGoogleAdsError('');
3082
+ setGoogleAdsSuccess('');
3083
+ }}
3084
+ className="cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700"
3085
+ >
3086
+ + Ajouter
3087
+ </button>
3088
+ </div>
3089
+
3090
+ {googleAdsSuccess && (
3091
+ <div className="mt-4 rounded-lg border border-green-200 bg-green-50 p-4">
3092
+ <div className="flex items-center">
3093
+ <svg
3094
+ className="mr-3 h-5 w-5 text-green-600"
3095
+ fill="none"
3096
+ stroke="currentColor"
3097
+ viewBox="0 0 24 24"
3098
+ >
3099
+ <path
3100
+ strokeLinecap="round"
3101
+ strokeLinejoin="round"
3102
+ strokeWidth={2}
3103
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
3104
+ />
3105
+ </svg>
3106
+ <p className="text-sm font-medium text-green-800">{googleAdsSuccess}</p>
3107
+ <button
3108
+ onClick={() => setGoogleAdsSuccess('')}
3109
+ className="ml-auto cursor-pointer text-green-600 hover:text-green-800"
3110
+ >
3111
+ <svg
3112
+ className="h-5 w-5"
3113
+ fill="none"
3114
+ stroke="currentColor"
3115
+ viewBox="0 0 24 24"
3116
+ >
3117
+ <path
3118
+ strokeLinecap="round"
3119
+ strokeLinejoin="round"
3120
+ strokeWidth={2}
3121
+ d="M6 18L18 6M6 6l12 12"
3122
+ />
3123
+ </svg>
3124
+ </button>
3125
+ </div>
3126
+ </div>
3127
+ )}
3128
+
3129
+ {googleAdsError && (
3130
+ <div className="mt-4 rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
3131
+ {googleAdsError}
3132
+ </div>
3133
+ )}
3134
+
3135
+ {googleAdsLoading ? (
3136
+ <div className="mt-6 text-center text-gray-500">Chargement...</div>
3137
+ ) : googleAdsConfigs.length === 0 ? (
3138
+ <div className="mt-6 rounded-lg border border-dashed border-gray-300 bg-gray-50 p-8 text-center">
3139
+ <p className="text-sm text-gray-600">Aucune configuration Google Ads</p>
3140
+ <p className="mt-1 text-xs text-gray-500">
3141
+ Cliquez sur &quot;+ Ajouter&quot; pour créer votre première
3142
+ configuration
3143
+ </p>
3144
+ </div>
3145
+ ) : (
3146
+ <div className="mt-6 space-y-3">
3147
+ {googleAdsConfigs.map((config) => (
3148
+ <div
3149
+ key={config.id}
3150
+ className="flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 p-4"
3151
+ >
3152
+ <div className="flex-1">
3153
+ <div className="flex items-center gap-2">
3154
+ <h3 className="font-medium text-gray-900">{config.name}</h3>
3155
+ {config.active ? (
3156
+ <span className="rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
3157
+ Actif
3158
+ </span>
3159
+ ) : (
3160
+ <span className="rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-800">
3161
+ Inactif
3162
+ </span>
3163
+ )}
3164
+ </div>
3165
+ <p className="mt-1 text-xs text-gray-500">
3166
+ Webhook Key: {config.webhookKey}
3167
+ </p>
3168
+ </div>
3169
+ <div className="flex items-center gap-2">
3170
+ <button
3171
+ onClick={() => handleEditGoogleAds(config)}
3172
+ className="cursor-pointer rounded-lg border border-gray-300 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100"
3173
+ >
3174
+ Modifier
3175
+ </button>
3176
+ <button
3177
+ onClick={() => handleDeleteGoogleAds(config.id)}
3178
+ className="cursor-pointer rounded-lg border border-indigo-300 px-3 py-1.5 text-xs font-medium text-indigo-700 transition-colors hover:bg-indigo-50"
3179
+ >
3180
+ Supprimer
3181
+ </button>
3182
+ </div>
3183
+ </div>
3184
+ ))}
3185
+ </div>
3186
+ )}
3187
+ </div>
3188
+ </div>
3189
+ ) : (
3190
+ <div className="rounded-lg bg-white p-6 shadow-sm">
3191
+ <div className="py-12 text-center">
3192
+ <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-indigo-100">
3193
+ <Plug className="h-6 w-6 text-indigo-600" />
3194
+ </div>
3195
+ <h3 className="mt-4 text-lg font-semibold text-gray-900">Accès restreint</h3>
3196
+ <p className="mt-2 text-sm text-gray-600">
3197
+ Cette section est réservée aux administrateurs.
3198
+ </p>
3199
+ </div>
3200
+ </div>
3201
+ )}
3202
+ </>
3203
+ )}
3204
+ </div>
3205
+ </div>
3206
+ </div>
3207
+
3208
+ {/* Modals - doivent être en dehors des sections conditionnelles */}
3209
+ {showGoogleSheetModal && (
3210
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-500/20 p-4 backdrop-blur-sm sm:p-6">
3211
+ <div className="flex max-h-[90vh] w-full max-w-5xl flex-col rounded-lg bg-white p-6 shadow-xl sm:p-8">
3212
+ {/* En-tête fixe */}
3213
+ <div className="shrink-0 border-b border-gray-100 pb-4">
3214
+ <div className="flex items-center justify-between">
3215
+ <h2 className="text-xl font-bold text-gray-900 sm:text-2xl">
3216
+ {editingGoogleSheetConfig ? 'Modifier' : 'Ajouter'} une configuration Google
3217
+ Sheets
3218
+ </h2>
3219
+ <button
3220
+ type="button"
3221
+ onClick={() => {
3222
+ setShowGoogleSheetModal(false);
3223
+ setEditingGoogleSheetConfig(null);
3224
+ setGoogleSheetStep(1);
3225
+ setGoogleSheetFormData({
3226
+ name: '',
3227
+ active: true,
3228
+ sheetUrl: '',
3229
+ sheetName: '',
3230
+ headerRow: '1',
3231
+ defaultStatusId: null,
3232
+ defaultAssignedUserId: null,
3233
+ });
3234
+ setGoogleSheetMappings([]);
3235
+ setGoogleSheetError('');
3236
+ setGoogleSheetPreview([]);
3237
+ setGoogleSheetHeaders([]);
3238
+ }}
3239
+ className="cursor-pointer rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100"
3240
+ >
3241
+ <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3242
+ <path
3243
+ strokeLinecap="round"
3244
+ strokeLinejoin="round"
3245
+ strokeWidth={2}
3246
+ d="M6 18L18 6M6 6l12 12"
3247
+ />
3248
+ </svg>
3249
+ </button>
3250
+ </div>
3251
+ </div>
3252
+
3253
+ {/* Contenu scrollable */}
3254
+ <form
3255
+ id="google-sheet-form"
3256
+ onSubmit={handleGoogleSheetSubmit}
3257
+ className="flex-1 space-y-4 overflow-y-auto pt-4 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
3258
+ >
3259
+ {/* Étape 1 : informations de base */}
3260
+ {googleSheetStep === 1 && (
3261
+ <>
3262
+ <div>
3263
+ <label className="block text-sm font-medium text-gray-700">
3264
+ Nom de la configuration *
3265
+ </label>
3266
+ <input
3267
+ type="text"
3268
+ required
3269
+ value={googleSheetFormData.name}
3270
+ onChange={(e) =>
3271
+ setGoogleSheetFormData((prev) => ({
3272
+ ...prev,
3273
+ name: e.target.value,
3274
+ }))
3275
+ }
3276
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3277
+ placeholder="Ex: Contacts Ventes"
3278
+ />
3279
+ </div>
3280
+
3281
+ <div className="flex items-center">
3282
+ <input
3283
+ id="google-sheet-active"
3284
+ type="checkbox"
3285
+ checked={googleSheetFormData.active}
3286
+ onChange={(e) =>
3287
+ setGoogleSheetFormData((prev) => ({
3288
+ ...prev,
3289
+ active: e.target.checked,
3290
+ }))
3291
+ }
3292
+ className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
3293
+ />
3294
+ <label
3295
+ htmlFor="google-sheet-active"
3296
+ className="ml-2 text-sm font-medium text-gray-700"
3297
+ >
3298
+ Activer l&apos;import automatique depuis Google Sheets
3299
+ </label>
3300
+ </div>
3301
+
3302
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
3303
+ <div className="md:col-span-2">
3304
+ <label className="block text-sm font-medium text-gray-700">
3305
+ Lien du Google Sheet *
3306
+ </label>
3307
+ <input
3308
+ type="url"
3309
+ required
3310
+ value={googleSheetFormData.sheetUrl}
3311
+ onChange={(e) => {
3312
+ setGoogleSheetFormData((prev) => ({
3313
+ ...prev,
3314
+ sheetUrl: e.target.value,
3315
+ }));
3316
+ setGoogleSheetPreview([]);
3317
+ setGoogleSheetHeaders([]);
3318
+ }}
3319
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3320
+ placeholder="https://docs.google.com/spreadsheets/d/..."
3321
+ />
3322
+ </div>
3323
+
3324
+ <div>
3325
+ <label className="block text-sm font-medium text-gray-700">
3326
+ Nom de l&apos;onglet (Sheet) *
3327
+ </label>
3328
+ <input
3329
+ type="text"
3330
+ required
3331
+ value={googleSheetFormData.sheetName}
3332
+ onChange={(e) => {
3333
+ setGoogleSheetFormData((prev) => ({
3334
+ ...prev,
3335
+ sheetName: e.target.value,
3336
+ }));
3337
+ setGoogleSheetPreview([]);
3338
+ setGoogleSheetHeaders([]);
3339
+ }}
3340
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3341
+ placeholder="Feuille 1"
3342
+ />
3343
+ </div>
3344
+
3345
+ <div>
3346
+ <label className="block text-sm font-medium text-gray-700">
3347
+ Ligne des en-têtes *
3348
+ </label>
3349
+ <input
3350
+ type="number"
3351
+ min={1}
3352
+ required
3353
+ value={googleSheetFormData.headerRow}
3354
+ onChange={(e) => {
3355
+ setGoogleSheetFormData((prev) => ({
3356
+ ...prev,
3357
+ headerRow: e.target.value,
3358
+ }));
3359
+ setGoogleSheetPreview([]);
3360
+ setGoogleSheetHeaders([]);
3361
+ }}
3362
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3363
+ placeholder="1"
3364
+ />
3365
+ </div>
3366
+ </div>
3367
+ </>
3368
+ )}
3369
+
3370
+ {/* Étape 2 : mapping manuel des colonnes + valeurs par défaut */}
3371
+ {googleSheetStep === 2 && (
3372
+ <>
3373
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
3374
+ <div>
3375
+ <label className="block text-sm font-medium text-gray-700">
3376
+ Utilisateur assigné par défaut (optionnel)
3377
+ </label>
3378
+ <select
3379
+ value={googleSheetFormData.defaultAssignedUserId || ''}
3380
+ onChange={(e) =>
3381
+ setGoogleSheetFormData((prev) => ({
3382
+ ...prev,
3383
+ defaultAssignedUserId: e.target.value || null,
3384
+ }))
3385
+ }
3386
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3387
+ >
3388
+ <option value="">Aucun utilisateur par défaut</option>
3389
+ {metaLeadUsers.map((user) => (
3390
+ <option key={user.id} value={user.id}>
3391
+ {user.name} ({user.email})
3392
+ </option>
3393
+ ))}
3394
+ </select>
3395
+ </div>
3396
+
3397
+ <div>
3398
+ <label className="block text-sm font-medium text-gray-700">
3399
+ Statut par défaut
3400
+ </label>
3401
+ <select
3402
+ value={googleSheetFormData.defaultStatusId || ''}
3403
+ onChange={(e) =>
3404
+ setGoogleSheetFormData((prev) => ({
3405
+ ...prev,
3406
+ defaultStatusId: e.target.value || null,
3407
+ }))
3408
+ }
3409
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3410
+ >
3411
+ <option value="">Aucun statut par défaut</option>
3412
+ {statuses.map((status) => (
3413
+ <option key={status.id} value={status.id}>
3414
+ {status.name}
3415
+ </option>
3416
+ ))}
3417
+ </select>
3418
+ </div>
3419
+ </div>
3420
+
3421
+ <div className="flex items-center justify-between">
3422
+ <h3 className="text-base font-semibold text-gray-900">
3423
+ Correspondance des champs
3424
+ </h3>
3425
+ <button
3426
+ type="button"
3427
+ onClick={() => {
3428
+ setGoogleSheetMappings((prev) => [
3429
+ ...prev,
3430
+ {
3431
+ id: `mapping-${Date.now()}-${Math.random()}`,
3432
+ columnName: '',
3433
+ action: 'ignore',
3434
+ },
3435
+ ]);
3436
+ }}
3437
+ className="flex cursor-pointer items-center gap-1 rounded-lg border border-indigo-600 bg-white px-3 py-1.5 text-sm font-medium text-indigo-600 transition-colors hover:bg-indigo-50"
3438
+ >
3439
+ <Plus className="h-4 w-4" />
3440
+ Ajouter un champ
3441
+ </button>
3442
+ </div>
3443
+
3444
+ <div className="mt-4 space-y-3">
3445
+ {googleSheetMappings.map((mapping) => (
3446
+ <div
3447
+ key={mapping.id}
3448
+ className="flex items-center gap-3 rounded-lg border border-gray-200 bg-gray-50 p-3"
3449
+ >
3450
+ <div className="flex-1">
3451
+ <input
3452
+ type="text"
3453
+ value={mapping.columnName}
3454
+ onChange={(e) => {
3455
+ setGoogleSheetMappings((prev) =>
3456
+ prev.map((m) =>
3457
+ m.id === mapping.id ? { ...m, columnName: e.target.value } : m,
3458
+ ),
3459
+ );
3460
+ }}
3461
+ placeholder="Nom de la colonne dans Google Sheets"
3462
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-900 placeholder:text-gray-400 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3463
+ />
3464
+ </div>
3465
+
3466
+ <div className="flex items-center">
3467
+ <ArrowRight className="h-5 w-5 text-gray-400" />
3468
+ </div>
3469
+
3470
+ <div className="flex-1">
3471
+ <select
3472
+ value={mapping.action}
3473
+ onChange={(e) => {
3474
+ const newAction = e.target.value as 'map' | 'note' | 'ignore';
3475
+ setGoogleSheetMappings((prev) =>
3476
+ prev.map((m) =>
3477
+ m.id === mapping.id
3478
+ ? {
3479
+ ...m,
3480
+ action: newAction,
3481
+ crmField:
3482
+ newAction === 'map' && m.crmField
3483
+ ? m.crmField
3484
+ : newAction === 'map'
3485
+ ? 'firstName'
3486
+ : undefined,
3487
+ }
3488
+ : m,
3489
+ ),
3490
+ );
3491
+ }}
3492
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3493
+ >
3494
+ <option value="map">Mapper vers un champ</option>
3495
+ <option value="note">Ajouter comme note</option>
3496
+ <option value="ignore">-- Ne pas importer --</option>
3497
+ </select>
3498
+ </div>
3499
+
3500
+ {mapping.action === 'map' && (
3501
+ <>
3502
+ <div className="flex items-center">
3503
+ <ArrowRight className="h-5 w-5 text-gray-400" />
3504
+ </div>
3505
+ <div className="flex-1">
3506
+ <select
3507
+ value={mapping.crmField || ''}
3508
+ onChange={(e) => {
3509
+ setGoogleSheetMappings((prev) =>
3510
+ prev.map((m) =>
3511
+ m.id === mapping.id ? { ...m, crmField: e.target.value } : m,
3512
+ ),
3513
+ );
3514
+ }}
3515
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3516
+ >
3517
+ <option value="">Sélectionnez un champ</option>
3518
+ <option value="phone">Téléphone *</option>
3519
+ <option value="firstName">Prénom</option>
3520
+ <option value="lastName">Nom</option>
3521
+ <option value="email">Email</option>
3522
+ <option value="civility">Civilité</option>
3523
+ <option value="secondaryPhone">Téléphone secondaire</option>
3524
+ <option value="address">Adresse</option>
3525
+ <option value="city">Ville</option>
3526
+ <option value="postalCode">Code postal</option>
3527
+ <option value="origin">Origine</option>
3528
+ </select>
3529
+ </div>
3530
+ </>
3531
+ )}
3532
+
3533
+ <button
3534
+ type="button"
3535
+ onClick={() => {
3536
+ setGoogleSheetMappings((prev) =>
3537
+ prev.filter((m) => m.id !== mapping.id),
3538
+ );
3539
+ }}
3540
+ className="cursor-pointer rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-200 hover:text-indigo-600"
3541
+ >
3542
+ <Trash2 className="h-4 w-4" />
3543
+ </button>
3544
+ </div>
3545
+ ))}
3546
+ </div>
3547
+
3548
+ <div className="mt-6 rounded-lg border border-gray-200 bg-gray-50 p-4">
3549
+ <ul className="space-y-2 text-xs text-gray-600">
3550
+ <li>
3551
+ • Le champ &quot;Téléphone&quot; est obligatoire pour l&#39;import.
3552
+ Configurez également les autres champs que vous souhaitez importer.
3553
+ </li>
3554
+ <li>
3555
+ • Les colonnes sans correspondance de champ sélectionnée (-- Ne pas importer
3556
+ --) seront ignorées lors de l&apos;import
3557
+ </li>
3558
+ <li>
3559
+ • Laissez vide le nom de colonne Google Sheets pour ignorer ce mapping
3560
+ </li>
3561
+ <li>
3562
+ • Pour &quot;Origine&quot;, utilisez le nom de la plateforme (ex: Facebook,
3563
+ Instagram, Google, LinkedIn, Site Web)
3564
+ </li>
3565
+ <li>
3566
+ • Les colonnes marquées &quot;Ajouter comme note&quot; seront regroupées
3567
+ dans une seule note avec le format &quot;Question : Réponse&quot;
3568
+ </li>
3569
+ </ul>
3570
+ </div>
3571
+
3572
+ {googleSheetPreview.length > 0 && googleSheetHeaders.length > 0 && (
3573
+ <div>
3574
+ <h3 className="mb-3 text-sm font-semibold text-gray-900">
3575
+ Aperçu (5 premières lignes)
3576
+ </h3>
3577
+ <div className="overflow-x-auto rounded-lg border border-gray-200">
3578
+ <table className="min-w-full divide-y divide-gray-200">
3579
+ <thead className="bg-gray-50">
3580
+ <tr>
3581
+ {googleSheetHeaders.map((header) => (
3582
+ <th
3583
+ key={header}
3584
+ className="px-4 py-2 text-left text-xs font-medium text-gray-700"
3585
+ >
3586
+ {header}
3587
+ </th>
3588
+ ))}
3589
+ </tr>
3590
+ </thead>
3591
+ <tbody className="divide-y divide-gray-200 bg-white">
3592
+ {googleSheetPreview.map((row, idx) => (
3593
+ <tr key={idx}>
3594
+ {googleSheetHeaders.map((header) => (
3595
+ <td
3596
+ key={header}
3597
+ className="px-4 py-2 text-xs whitespace-nowrap text-gray-900"
3598
+ >
3599
+ {row[header] || '-'}
3600
+ </td>
3601
+ ))}
3602
+ </tr>
3603
+ ))}
3604
+ </tbody>
3605
+ </table>
3606
+ </div>
3607
+ </div>
3608
+ )}
3609
+ </>
3610
+ )}
3611
+
3612
+ {googleSheetError && (
3613
+ <div className="rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
3614
+ {googleSheetError}
3615
+ </div>
3616
+ )}
3617
+ </form>
3618
+
3619
+ {/* Pied de modal fixe */}
3620
+ <div className="shrink-0 border-t border-gray-100 pt-4">
3621
+ <div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
3622
+ <button
3623
+ type="button"
3624
+ onClick={() => {
3625
+ setShowGoogleSheetModal(false);
3626
+ setEditingGoogleSheetConfig(null);
3627
+ setGoogleSheetStep(1);
3628
+ setGoogleSheetFormData({
3629
+ name: '',
3630
+ active: true,
3631
+ sheetUrl: '',
3632
+ sheetName: '',
3633
+ headerRow: '1',
3634
+ defaultStatusId: null,
3635
+ defaultAssignedUserId: null,
3636
+ });
3637
+ setGoogleSheetMappings([]);
3638
+ setGoogleSheetError('');
3639
+ setGoogleSheetPreview([]);
3640
+ setGoogleSheetHeaders([]);
3641
+ }}
3642
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
3643
+ >
3644
+ Annuler
3645
+ </button>
3646
+
3647
+ {googleSheetStep === 1 ? (
3648
+ <button
3649
+ type="button"
3650
+ onClick={async () => {
3651
+ const ok = await handleGoogleSheetAutoMap();
3652
+ if (ok) {
3653
+ setGoogleSheetStep(2);
3654
+ }
3655
+ }}
3656
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
3657
+ disabled={googleSheetSaving}
3658
+ >
3659
+ Étape suivante
3660
+ </button>
3661
+ ) : (
3662
+ <button
3663
+ type="submit"
3664
+ form="google-sheet-form"
3665
+ disabled={googleSheetSaving}
3666
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
3667
+ >
3668
+ {googleSheetSaving ? 'Enregistrement...' : 'Enregistrer'}
3669
+ </button>
3670
+ )}
3671
+ </div>
3672
+ </div>
3673
+ </div>
3674
+ </div>
3675
+ )}
3676
+
3677
+ {showMetaLeadModal && (
3678
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-500/20 p-4 backdrop-blur-sm sm:p-6">
3679
+ <div className="flex max-h-[90vh] w-full max-w-2xl flex-col rounded-lg bg-white p-6 shadow-xl sm:p-8">
3680
+ <div className="shrink-0 border-b border-gray-100 pb-4">
3681
+ <div className="flex items-center justify-between">
3682
+ <h2 className="text-xl font-bold text-gray-900 sm:text-2xl">
3683
+ {editingMetaLeadConfig ? 'Modifier' : 'Ajouter'} une configuration Meta Lead Ads
3684
+ </h2>
3685
+ <button
3686
+ type="button"
3687
+ onClick={() => {
3688
+ setShowMetaLeadModal(false);
3689
+ setEditingMetaLeadConfig(null);
3690
+ setMetaLeadFormData({
3691
+ name: '',
3692
+ active: true,
3693
+ pageId: '',
3694
+ accessToken: '',
3695
+ verifyToken: '',
3696
+ defaultStatusId: null,
3697
+ defaultAssignedUserId: null,
3698
+ });
3699
+ setMetaLeadError('');
3700
+ setMetaLeadSuccess('');
3701
+ }}
3702
+ className="cursor-pointer rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100"
3703
+ >
3704
+ <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3705
+ <path
3706
+ strokeLinecap="round"
3707
+ strokeLinejoin="round"
3708
+ strokeWidth={2}
3709
+ d="M6 18L18 6M6 6l12 12"
3710
+ />
3711
+ </svg>
3712
+ </button>
3713
+ </div>
3714
+ </div>
3715
+
3716
+ <form
3717
+ id="meta-lead-form"
3718
+ onSubmit={handleMetaLeadSubmit}
3719
+ className="flex-1 space-y-4 overflow-y-auto pt-4"
3720
+ >
3721
+ <div>
3722
+ <label className="block text-sm font-medium text-gray-700">
3723
+ Nom de la configuration *
3724
+ </label>
3725
+ <input
3726
+ type="text"
3727
+ required
3728
+ value={metaLeadFormData.name}
3729
+ onChange={(e) =>
3730
+ setMetaLeadFormData((prev) => ({ ...prev, name: e.target.value }))
3731
+ }
3732
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3733
+ placeholder="Ex: Facebook Lead Ads"
3734
+ />
3735
+ </div>
3736
+
3737
+ <div className="flex items-center">
3738
+ <input
3739
+ id="meta-lead-active"
3740
+ type="checkbox"
3741
+ checked={metaLeadFormData.active}
3742
+ onChange={(e) =>
3743
+ setMetaLeadFormData((prev) => ({ ...prev, active: e.target.checked }))
3744
+ }
3745
+ className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
3746
+ />
3747
+ <label
3748
+ htmlFor="meta-lead-active"
3749
+ className="ml-2 text-sm font-medium text-gray-700"
3750
+ >
3751
+ Activer l&apos;intégration Meta Lead Ads
3752
+ </label>
3753
+ </div>
3754
+
3755
+ <div>
3756
+ <label className="block text-sm font-medium text-gray-700">Page ID *</label>
3757
+ <input
3758
+ type="text"
3759
+ required
3760
+ value={metaLeadFormData.pageId}
3761
+ onChange={(e) =>
3762
+ setMetaLeadFormData((prev) => ({ ...prev, pageId: e.target.value }))
3763
+ }
3764
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3765
+ placeholder="Page ID Facebook"
3766
+ />
3767
+ </div>
3768
+
3769
+ <div>
3770
+ <label className="block text-sm font-medium text-gray-700">Access Token *</label>
3771
+ <input
3772
+ type="text"
3773
+ required
3774
+ value={metaLeadFormData.accessToken}
3775
+ onChange={(e) =>
3776
+ setMetaLeadFormData((prev) => ({ ...prev, accessToken: e.target.value }))
3777
+ }
3778
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3779
+ placeholder="Access Token"
3780
+ />
3781
+ </div>
3782
+
3783
+ <div>
3784
+ <label className="block text-sm font-medium text-gray-700">Verify Token *</label>
3785
+ <input
3786
+ type="text"
3787
+ required
3788
+ value={metaLeadFormData.verifyToken}
3789
+ onChange={(e) =>
3790
+ setMetaLeadFormData((prev) => ({ ...prev, verifyToken: e.target.value }))
3791
+ }
3792
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3793
+ placeholder="Verify Token"
3794
+ />
3795
+ </div>
3796
+
3797
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
3798
+ <div>
3799
+ <label className="block text-sm font-medium text-gray-700">
3800
+ Utilisateur assigné par défaut (optionnel)
3801
+ </label>
3802
+ <select
3803
+ value={metaLeadFormData.defaultAssignedUserId || ''}
3804
+ onChange={(e) =>
3805
+ setMetaLeadFormData((prev) => ({
3806
+ ...prev,
3807
+ defaultAssignedUserId: e.target.value || null,
3808
+ }))
3809
+ }
3810
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3811
+ >
3812
+ <option value="">Aucun utilisateur par défaut</option>
3813
+ {metaLeadUsers.map((user) => (
3814
+ <option key={user.id} value={user.id}>
3815
+ {user.name} ({user.email})
3816
+ </option>
3817
+ ))}
3818
+ </select>
3819
+ </div>
3820
+
3821
+ <div>
3822
+ <label className="block text-sm font-medium text-gray-700">
3823
+ Statut par défaut
3824
+ </label>
3825
+ <select
3826
+ value={metaLeadFormData.defaultStatusId || ''}
3827
+ onChange={(e) =>
3828
+ setMetaLeadFormData((prev) => ({
3829
+ ...prev,
3830
+ defaultStatusId: e.target.value || null,
3831
+ }))
3832
+ }
3833
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3834
+ >
3835
+ <option value="">Aucun statut par défaut</option>
3836
+ {statuses.map((status) => (
3837
+ <option key={status.id} value={status.id}>
3838
+ {status.name}
3839
+ </option>
3840
+ ))}
3841
+ </select>
3842
+ </div>
3843
+ </div>
3844
+
3845
+ {metaLeadError && (
3846
+ <div className="rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
3847
+ {metaLeadError}
3848
+ </div>
3849
+ )}
3850
+ </form>
3851
+
3852
+ <div className="shrink-0 border-t border-gray-100 pt-4">
3853
+ <div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
3854
+ <button
3855
+ type="button"
3856
+ onClick={() => {
3857
+ setShowMetaLeadModal(false);
3858
+ setEditingMetaLeadConfig(null);
3859
+ setMetaLeadFormData({
3860
+ name: '',
3861
+ active: true,
3862
+ pageId: '',
3863
+ accessToken: '',
3864
+ verifyToken: '',
3865
+ defaultStatusId: null,
3866
+ defaultAssignedUserId: null,
3867
+ });
3868
+ setMetaLeadError('');
3869
+ setMetaLeadSuccess('');
3870
+ }}
3871
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
3872
+ >
3873
+ Annuler
3874
+ </button>
3875
+ <button
3876
+ type="submit"
3877
+ form="meta-lead-form"
3878
+ disabled={metaLeadSaving}
3879
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
3880
+ >
3881
+ {metaLeadSaving ? 'Enregistrement...' : 'Enregistrer'}
3882
+ </button>
3883
+ </div>
3884
+ </div>
3885
+ </div>
3886
+ </div>
3887
+ )}
3888
+
3889
+ {showGoogleAdsModal && (
3890
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-500/20 p-4 backdrop-blur-sm sm:p-6">
3891
+ <div className="flex max-h-[90vh] w-full max-w-2xl flex-col rounded-lg bg-white p-6 shadow-xl sm:p-8">
3892
+ <div className="shrink-0 border-b border-gray-100 pb-4">
3893
+ <div className="flex items-center justify-between">
3894
+ <h2 className="text-xl font-bold text-gray-900 sm:text-2xl">
3895
+ {editingGoogleAdsConfig ? 'Modifier' : 'Ajouter'} une configuration Google Ads
3896
+ </h2>
3897
+ <button
3898
+ type="button"
3899
+ onClick={() => {
3900
+ setShowGoogleAdsModal(false);
3901
+ setEditingGoogleAdsConfig(null);
3902
+ setGoogleAdsFormData({
3903
+ name: '',
3904
+ active: true,
3905
+ webhookKey: '',
3906
+ defaultStatusId: null,
3907
+ defaultAssignedUserId: null,
3908
+ });
3909
+ setGoogleAdsError('');
3910
+ setGoogleAdsSuccess('');
3911
+ }}
3912
+ className="cursor-pointer rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100"
3913
+ >
3914
+ <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3915
+ <path
3916
+ strokeLinecap="round"
3917
+ strokeLinejoin="round"
3918
+ strokeWidth={2}
3919
+ d="M6 18L18 6M6 6l12 12"
3920
+ />
3921
+ </svg>
3922
+ </button>
3923
+ </div>
3924
+ </div>
3925
+
3926
+ <form
3927
+ id="google-ads-form"
3928
+ onSubmit={handleGoogleAdsSubmit}
3929
+ className="flex-1 space-y-4 overflow-y-auto pt-4"
3930
+ >
3931
+ <div>
3932
+ <label className="block text-sm font-medium text-gray-700">
3933
+ Nom de la configuration *
3934
+ </label>
3935
+ <input
3936
+ type="text"
3937
+ required
3938
+ value={googleAdsFormData.name}
3939
+ onChange={(e) =>
3940
+ setGoogleAdsFormData((prev) => ({ ...prev, name: e.target.value }))
3941
+ }
3942
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3943
+ placeholder="Ex: Google Ads Lead Forms"
3944
+ />
3945
+ </div>
3946
+
3947
+ <div className="flex items-center">
3948
+ <input
3949
+ id="google-ads-active"
3950
+ type="checkbox"
3951
+ checked={googleAdsFormData.active}
3952
+ onChange={(e) =>
3953
+ setGoogleAdsFormData((prev) => ({ ...prev, active: e.target.checked }))
3954
+ }
3955
+ className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
3956
+ />
3957
+ <label
3958
+ htmlFor="google-ads-active"
3959
+ className="ml-2 text-sm font-medium text-gray-700"
3960
+ >
3961
+ Activer l&apos;intégration Google Ads
3962
+ </label>
3963
+ </div>
3964
+
3965
+ <div>
3966
+ <label className="block text-sm font-medium text-gray-700">Webhook Key *</label>
3967
+ <input
3968
+ type="text"
3969
+ required
3970
+ value={googleAdsFormData.webhookKey}
3971
+ onChange={(e) =>
3972
+ setGoogleAdsFormData((prev) => ({ ...prev, webhookKey: e.target.value }))
3973
+ }
3974
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3975
+ placeholder="Webhook Key"
3976
+ />
3977
+ </div>
3978
+
3979
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
3980
+ <div>
3981
+ <label className="block text-sm font-medium text-gray-700">
3982
+ Utilisateur assigné par défaut (optionnel)
3983
+ </label>
3984
+ <select
3985
+ value={googleAdsFormData.defaultAssignedUserId || ''}
3986
+ onChange={(e) =>
3987
+ setGoogleAdsFormData((prev) => ({
3988
+ ...prev,
3989
+ defaultAssignedUserId: e.target.value || null,
3990
+ }))
3991
+ }
3992
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
3993
+ >
3994
+ <option value="">Aucun utilisateur par défaut</option>
3995
+ {metaLeadUsers.map((user) => (
3996
+ <option key={user.id} value={user.id}>
3997
+ {user.name} ({user.email})
3998
+ </option>
3999
+ ))}
4000
+ </select>
4001
+ </div>
4002
+
4003
+ <div>
4004
+ <label className="block text-sm font-medium text-gray-700">
4005
+ Statut par défaut
4006
+ </label>
4007
+ <select
4008
+ value={googleAdsFormData.defaultStatusId || ''}
4009
+ onChange={(e) =>
4010
+ setGoogleAdsFormData((prev) => ({
4011
+ ...prev,
4012
+ defaultStatusId: e.target.value || null,
4013
+ }))
4014
+ }
4015
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
4016
+ >
4017
+ <option value="">Aucun statut par défaut</option>
4018
+ {statuses.map((status) => (
4019
+ <option key={status.id} value={status.id}>
4020
+ {status.name}
4021
+ </option>
4022
+ ))}
4023
+ </select>
4024
+ </div>
4025
+ </div>
4026
+
4027
+ {googleAdsError && (
4028
+ <div className="rounded-lg bg-indigo-50 p-4 text-sm text-indigo-600">
4029
+ {googleAdsError}
4030
+ </div>
4031
+ )}
4032
+ </form>
4033
+
4034
+ <div className="shrink-0 border-t border-gray-100 pt-4">
4035
+ <div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
4036
+ <button
4037
+ type="button"
4038
+ onClick={() => {
4039
+ setShowGoogleAdsModal(false);
4040
+ setEditingGoogleAdsConfig(null);
4041
+ setGoogleAdsFormData({
4042
+ name: '',
4043
+ active: true,
4044
+ webhookKey: '',
4045
+ defaultStatusId: null,
4046
+ defaultAssignedUserId: null,
4047
+ });
4048
+ setGoogleAdsError('');
4049
+ setGoogleAdsSuccess('');
4050
+ }}
4051
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
4052
+ >
4053
+ Annuler
4054
+ </button>
4055
+ <button
4056
+ type="submit"
4057
+ form="google-ads-form"
4058
+ disabled={googleAdsSaving}
4059
+ className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
4060
+ >
4061
+ {googleAdsSaving ? 'Enregistrement...' : 'Enregistrer'}
4062
+ </button>
4063
+ </div>
4064
+ </div>
4065
+ </div>
4066
+ </div>
4067
+ )}
4068
+ </div>
4069
+ );
4070
+ }