create-crm-tmp 1.1.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/bin/create-crm-tmp.js +56 -35
  2. package/package.json +1 -1
  3. package/template/.prettierignore +2 -0
  4. package/template/README.md +230 -115
  5. package/template/components.json +22 -0
  6. package/template/eslint.config.mjs +13 -0
  7. package/template/exemple-contacts.csv +54 -0
  8. package/template/next.config.ts +41 -1
  9. package/template/package.json +63 -15
  10. package/template/prisma/migrations/20260318095700_init_db/migration.sql +978 -0
  11. package/template/prisma/schema.prisma +311 -67
  12. package/template/src/app/(auth)/invite/[token]/page.tsx +28 -29
  13. package/template/src/app/(auth)/layout.tsx +1 -1
  14. package/template/src/app/(auth)/reset-password/complete/page.tsx +21 -27
  15. package/template/src/app/(auth)/reset-password/page.tsx +14 -10
  16. package/template/src/app/(auth)/reset-password/verify/page.tsx +14 -10
  17. package/template/src/app/(auth)/signin/page.tsx +34 -23
  18. package/template/src/app/(dashboard)/agenda/page.tsx +3655 -2357
  19. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
  20. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +609 -338
  21. package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
  22. package/template/src/app/(dashboard)/automatisation/page.tsx +463 -186
  23. package/template/src/app/(dashboard)/closing/page.tsx +517 -469
  24. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6151 -4210
  25. package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1702 -0
  26. package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
  27. package/template/src/app/(dashboard)/contacts/page.tsx +4124 -2130
  28. package/template/src/app/(dashboard)/dashboard/page.tsx +119 -105
  29. package/template/src/app/(dashboard)/dev/page.tsx +1291 -0
  30. package/template/src/app/(dashboard)/error.tsx +37 -0
  31. package/template/src/app/(dashboard)/layout.tsx +6 -2
  32. package/template/src/app/(dashboard)/loading.tsx +5 -0
  33. package/template/src/app/(dashboard)/settings/loading.tsx +19 -0
  34. package/template/src/app/(dashboard)/settings/page.tsx +1773 -3362
  35. package/template/src/app/(dashboard)/templates/page.tsx +504 -303
  36. package/template/src/app/(dashboard)/users/list/page.tsx +364 -355
  37. package/template/src/app/(dashboard)/users/page.tsx +279 -310
  38. package/template/src/app/(dashboard)/users/permissions/page.tsx +104 -99
  39. package/template/src/app/(dashboard)/users/roles/page.tsx +169 -140
  40. package/template/src/app/api/agenda/google-events/route.ts +92 -0
  41. package/template/src/app/api/audit-logs/route.ts +1 -1
  42. package/template/src/app/api/auth/check-active/route.ts +3 -2
  43. package/template/src/app/api/auth/google/callback/route.ts +8 -5
  44. package/template/src/app/api/auth/google/disconnect/route.ts +2 -2
  45. package/template/src/app/api/auth/google/route.ts +2 -1
  46. package/template/src/app/api/auth/google/status/route.ts +7 -31
  47. package/template/src/app/api/companies/[id]/activities/route.ts +129 -0
  48. package/template/src/app/api/companies/[id]/route.ts +194 -0
  49. package/template/src/app/api/companies/export/route.ts +206 -0
  50. package/template/src/app/api/companies/route.ts +196 -0
  51. package/template/src/app/api/contact-views/[id]/pin/route.ts +69 -0
  52. package/template/src/app/api/contact-views/[id]/route.ts +197 -0
  53. package/template/src/app/api/contact-views/route.ts +146 -0
  54. package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +55 -0
  55. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +20 -48
  56. package/template/src/app/api/contacts/[id]/files/route.ts +125 -186
  57. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +27 -1
  58. package/template/src/app/api/contacts/[id]/interactions/route.ts +45 -8
  59. package/template/src/app/api/contacts/[id]/kyc/route.ts +81 -0
  60. package/template/src/app/api/contacts/[id]/meet/route.ts +55 -29
  61. package/template/src/app/api/contacts/[id]/route.ts +184 -21
  62. package/template/src/app/api/contacts/[id]/send-email/route.ts +33 -11
  63. package/template/src/app/api/contacts/[id]/workflows/run/route.ts +67 -0
  64. package/template/src/app/api/contacts/export/route.ts +22 -31
  65. package/template/src/app/api/contacts/import/route.ts +77 -44
  66. package/template/src/app/api/contacts/import-preview/route.ts +139 -0
  67. package/template/src/app/api/contacts/origins/route.ts +63 -0
  68. package/template/src/app/api/contacts/route.ts +322 -57
  69. package/template/src/app/api/cron/cleanup-editor-images/route.ts +166 -0
  70. package/template/src/app/api/dashboard/stats/route.ts +9 -292
  71. package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -3
  72. package/template/src/app/api/dashboard/widgets/route.ts +19 -19
  73. package/template/src/app/api/dev/reminders/test/route.ts +114 -0
  74. package/template/src/app/api/editor/upload-image/route.ts +61 -0
  75. package/template/src/app/api/integrations/google-sheet/jobs/[jobId]/route.ts +47 -0
  76. package/template/src/app/api/integrations/google-sheet/jobs/usage/route.ts +50 -0
  77. package/template/src/app/api/integrations/google-sheet/sync/route.ts +28 -542
  78. package/template/src/app/api/invite/complete/route.ts +20 -23
  79. package/template/src/app/api/jobs/google-sheet/process/route.ts +84 -0
  80. package/template/src/app/api/jobs/google-sheet/schedule/route.ts +50 -0
  81. package/template/src/app/api/reminders/clear/route.ts +120 -0
  82. package/template/src/app/api/reminders/clear/undo/route.ts +112 -0
  83. package/template/src/app/api/reminders/route.ts +165 -39
  84. package/template/src/app/api/reminders/state/route.ts +164 -0
  85. package/template/src/app/api/reset-password/complete/route.ts +11 -13
  86. package/template/src/app/api/reset-password/request/route.ts +1 -1
  87. package/template/src/app/api/reset-password/verify/route.ts +1 -1
  88. package/template/src/app/api/send/route.ts +25 -47
  89. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +10 -21
  90. package/template/src/app/api/settings/closing-reasons/route.ts +10 -21
  91. package/template/src/app/api/settings/company/route.ts +19 -26
  92. package/template/src/app/api/settings/google-ads/[id]/route.ts +20 -23
  93. package/template/src/app/api/settings/google-ads/route.ts +34 -23
  94. package/template/src/app/api/settings/google-calendar/calendars/route.ts +97 -0
  95. package/template/src/app/api/settings/google-calendar/route.ts +124 -0
  96. package/template/src/app/api/settings/google-sheet/[id]/route.ts +48 -23
  97. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +56 -32
  98. package/template/src/app/api/settings/google-sheet/preview/route.ts +110 -0
  99. package/template/src/app/api/settings/google-sheet/route.ts +34 -23
  100. package/template/src/app/api/settings/integrations/logs/route.ts +93 -0
  101. package/template/src/app/api/settings/integrations/notifications/route.ts +67 -0
  102. package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -24
  103. package/template/src/app/api/settings/meta-leads/route.ts +34 -25
  104. package/template/src/app/api/settings/smtp/route.ts +53 -6
  105. package/template/src/app/api/settings/statuses/[id]/route.ts +29 -32
  106. package/template/src/app/api/settings/statuses/route.ts +24 -22
  107. package/template/src/app/api/statuses/route.ts +2 -5
  108. package/template/src/app/api/tasks/[id]/attendees/route.ts +36 -13
  109. package/template/src/app/api/tasks/[id]/route.ts +357 -145
  110. package/template/src/app/api/tasks/meet/route.ts +37 -26
  111. package/template/src/app/api/tasks/route.ts +201 -96
  112. package/template/src/app/api/templates/[id]/route.ts +22 -13
  113. package/template/src/app/api/templates/route.ts +22 -5
  114. package/template/src/app/api/users/[id]/resend-invite/route.ts +95 -0
  115. package/template/src/app/api/users/[id]/route.ts +22 -16
  116. package/template/src/app/api/users/commercials/route.ts +38 -0
  117. package/template/src/app/api/users/for-agenda/route.ts +1 -2
  118. package/template/src/app/api/users/list/route.ts +57 -19
  119. package/template/src/app/api/users/route.ts +89 -34
  120. package/template/src/app/api/webhooks/google-ads/route.ts +40 -1
  121. package/template/src/app/api/webhooks/meta-leads/route.ts +38 -1
  122. package/template/src/app/api/workflows/[id]/route.ts +29 -6
  123. package/template/src/app/api/workflows/process/route.ts +505 -170
  124. package/template/src/app/api/workflows/route.ts +42 -4
  125. package/template/src/app/globals.css +512 -32
  126. package/template/src/app/layout.tsx +28 -9
  127. package/template/src/app/page.tsx +37 -7
  128. package/template/src/components/address-autocomplete.tsx +233 -0
  129. package/template/src/components/config-error-alert.tsx +46 -0
  130. package/template/src/components/contacts/filter-bar.tsx +190 -0
  131. package/template/src/components/contacts/filter-builder.tsx +574 -0
  132. package/template/src/components/contacts/save-view-dialog.tsx +160 -0
  133. package/template/src/components/contacts/views-tab-bar.tsx +449 -0
  134. package/template/src/components/dashboard/activity-chart.tsx +6 -1
  135. package/template/src/components/dashboard/add-widget-dialog.tsx +13 -17
  136. package/template/src/components/dashboard/color-picker.tsx +7 -8
  137. package/template/src/components/dashboard/recent-activity.tsx +2 -5
  138. package/template/src/components/dashboard/stat-card.tsx +1 -3
  139. package/template/src/components/dashboard/status-distribution-chart.tsx +0 -1
  140. package/template/src/components/dashboard/top-contacts-list.tsx +7 -13
  141. package/template/src/components/dashboard/upcoming-tasks-list.tsx +2 -5
  142. package/template/src/components/dashboard/widget-wrapper.tsx +3 -6
  143. package/template/src/components/date-picker.tsx +399 -0
  144. package/template/src/components/editor/upload-editor-image.ts +42 -0
  145. package/template/src/components/editor.tsx +188 -35
  146. package/template/src/components/email-template.tsx +4 -2
  147. package/template/src/components/global-search.tsx +360 -0
  148. package/template/src/components/header.tsx +200 -107
  149. package/template/src/components/inactive-account-guard.tsx +58 -0
  150. package/template/src/components/integration-notifications-listener.tsx +12 -0
  151. package/template/src/components/invitation-email-template.tsx +4 -2
  152. package/template/src/components/lazy-editor.tsx +11 -0
  153. package/template/src/components/meet-cancellation-email-template.tsx +11 -3
  154. package/template/src/components/meet-confirmation-email-template.tsx +10 -3
  155. package/template/src/components/meet-update-email-template.tsx +10 -3
  156. package/template/src/components/page-header.tsx +19 -15
  157. package/template/src/components/protected-page.tsx +94 -0
  158. package/template/src/components/reset-password-email-template.tsx +4 -2
  159. package/template/src/components/settings/integrations/GoogleAdsIntegration.tsx +428 -0
  160. package/template/src/components/settings/integrations/GoogleSheetConfigMonitoringModal.tsx +680 -0
  161. package/template/src/components/settings/integrations/GoogleSheetIntegration.tsx +809 -0
  162. package/template/src/components/settings/integrations/ImportResultDialog.tsx +124 -0
  163. package/template/src/components/settings/integrations/IntegrationLogPanel.tsx +57 -0
  164. package/template/src/components/settings/integrations/IntegrationLogsTable.tsx +186 -0
  165. package/template/src/components/settings/integrations/MetaLeadIntegration.tsx +451 -0
  166. package/template/src/components/sidebar.tsx +117 -100
  167. package/template/src/components/skeleton.tsx +128 -45
  168. package/template/src/components/ui/accordion.tsx +64 -0
  169. package/template/src/components/ui/alert-dialog.tsx +139 -0
  170. package/template/src/components/ui/button.tsx +71 -0
  171. package/template/src/components/ui/components.tsx +1 -1
  172. package/template/src/components/ui/date-picker.tsx +422 -0
  173. package/template/src/components/ui/datetime-picker.tsx +338 -0
  174. package/template/src/components/ui/status-select.tsx +271 -0
  175. package/template/src/components/ui/tooltip.tsx +37 -0
  176. package/template/src/components/view-as-banner.tsx +1 -1
  177. package/template/src/components/view-as-modal.tsx +30 -19
  178. package/template/src/config/nav-pages.ts +108 -0
  179. package/template/src/contexts/app-toast-context.tsx +362 -0
  180. package/template/src/contexts/dashboard-theme-context.tsx +2 -7
  181. package/template/src/contexts/sidebar-context.tsx +27 -53
  182. package/template/src/contexts/task-reminder-context.tsx +134 -160
  183. package/template/src/contexts/view-as-context.tsx +32 -10
  184. package/template/src/hooks/use-alert.tsx +65 -0
  185. package/template/src/hooks/use-confirm.tsx +87 -0
  186. package/template/src/hooks/use-contact-views.ts +140 -0
  187. package/template/src/hooks/use-contacts.ts +69 -0
  188. package/template/src/hooks/use-fetch.ts +17 -0
  189. package/template/src/hooks/use-focus-trap.ts +73 -0
  190. package/template/src/hooks/use-statuses.ts +22 -0
  191. package/template/src/hooks/useIntegrationNotifications.ts +49 -0
  192. package/template/src/lib/address-api.ts +155 -0
  193. package/template/src/lib/auth.ts +8 -1
  194. package/template/src/lib/cache.ts +73 -0
  195. package/template/src/lib/check-permission.ts +12 -177
  196. package/template/src/lib/config-links.ts +14 -0
  197. package/template/src/lib/contact-duplicate.ts +79 -61
  198. package/template/src/lib/contact-interactions.ts +24 -22
  199. package/template/src/lib/contact-view-filters.ts +301 -0
  200. package/template/src/lib/contacts-list-url.ts +190 -0
  201. package/template/src/lib/dashboard-stats.ts +282 -0
  202. package/template/src/lib/dashboard-themes.ts +0 -5
  203. package/template/src/lib/date-utils.ts +176 -0
  204. package/template/src/lib/default-widgets.ts +0 -2
  205. package/template/src/lib/editor-html-image-dimensions.ts +172 -0
  206. package/template/src/lib/editor-image-limits.ts +19 -0
  207. package/template/src/lib/email-html-sanitize.ts +19 -0
  208. package/template/src/lib/encryption.ts +9 -6
  209. package/template/src/lib/fr-geography.ts +192 -0
  210. package/template/src/lib/get-auth-user.ts +25 -0
  211. package/template/src/lib/google-calendar-agenda.ts +201 -0
  212. package/template/src/lib/google-calendar.ts +309 -17
  213. package/template/src/lib/google-fetch.ts +63 -0
  214. package/template/src/lib/google-sheet-sync-jobs.ts +96 -0
  215. package/template/src/lib/google-sheet-sync-runner.ts +514 -0
  216. package/template/src/lib/integration-import-log.ts +21 -0
  217. package/template/src/lib/local-storage.ts +34 -0
  218. package/template/src/lib/permissions.ts +268 -40
  219. package/template/src/lib/prisma.ts +15 -12
  220. package/template/src/lib/qstash.ts +65 -0
  221. package/template/src/lib/reminder-state-server.ts +80 -0
  222. package/template/src/lib/reminder-state.ts +29 -0
  223. package/template/src/lib/roles.ts +12 -15
  224. package/template/src/lib/supabase-storage.ts +113 -0
  225. package/template/src/lib/template-variables.ts +204 -29
  226. package/template/src/lib/utils.ts +71 -11
  227. package/template/src/lib/widget-registry.ts +0 -4
  228. package/template/src/lib/workflow-executor.ts +391 -228
  229. package/template/src/proxy.ts +35 -73
  230. package/template/src/types/contact-views.ts +351 -0
  231. package/template/vercel.json +5 -0
  232. package/template/WORKFLOWS_CRON.md +0 -185
  233. package/template/prisma/migrations/20251126144728_init/migration.sql +0 -78
  234. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +0 -5
  235. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +0 -19
  236. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +0 -22
  237. package/template/prisma/migrations/20251128132303_add_status/migration.sql +0 -23
  238. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +0 -75
  239. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +0 -2
  240. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +0 -45
  241. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +0 -2
  242. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +0 -27
  243. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +0 -20
  244. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +0 -18
  245. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +0 -32
  246. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +0 -20
  247. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +0 -12
  248. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +0 -21
  249. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +0 -11
  250. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +0 -12
  251. package/template/prisma/migrations/20251208094843_mg/migration.sql +0 -14
  252. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +0 -14
  253. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +0 -26
  254. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +0 -2
  255. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +0 -2
  256. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +0 -2
  257. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +0 -3
  258. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +0 -21
  259. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +0 -2
  260. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +0 -10
  261. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +0 -26
  262. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +0 -24
  263. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +0 -11
  264. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +0 -12
  265. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +0 -25
  266. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +0 -8
  267. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +0 -2
  268. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +0 -80
  269. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +0 -32
  270. package/template/prisma/migrations/20251220000000_add_task_interaction_type/migration.sql +0 -4
  271. package/template/prisma/migrations/20251221000000_add_task_type/migration.sql +0 -3
  272. package/template/prisma/migrations/20251221000001_add_event_color/migration.sql +0 -23
  273. package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +0 -20
  274. package/template/prisma/migrations/20260226093949_fix_cascade_on_user_delete/migration.sql +0 -69
  275. package/template/src/app/(dashboard)/users/layout.tsx +0 -30
  276. package/template/src/lib/google-drive.ts +0 -380
@@ -1,11 +1,33 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useState } from 'react';
4
- import Link from 'next/link';
5
4
  import { useRouter } from 'next/navigation';
6
5
  import { PageHeader } from '@/components/page-header';
7
- import { Plus, Edit, Trash2, Play, Pause, Search } from 'lucide-react';
8
- import { cn } from '@/lib/utils';
6
+ import { PageLoader } from '@/components/skeleton';
7
+ import {
8
+ Plus,
9
+ Trash2,
10
+ Search,
11
+ ChevronDown,
12
+ ChevronUp,
13
+ Zap,
14
+ Mail,
15
+ MessageSquare,
16
+ Tag,
17
+ CheckSquare,
18
+ Clock,
19
+ UserPlus,
20
+ StickyNote,
21
+ Bell,
22
+ Users,
23
+ Filter,
24
+ Info,
25
+ } from 'lucide-react';
26
+ import { cn, devToast } from '@/lib/utils';
27
+ import { ProtectedPage } from '@/components/protected-page';
28
+ import { useConfirm } from '@/hooks/use-confirm';
29
+ import { useUserRole } from '@/hooks/use-user-role';
30
+ import { useAppToast } from '@/contexts/app-toast-context';
9
31
 
10
32
  interface Status {
11
33
  id: string;
@@ -18,7 +40,7 @@ interface Workflow {
18
40
  name: string;
19
41
  description: string | null;
20
42
  active: boolean;
21
- triggerType: 'CONTACT_CREATED' | 'STATUS_CHANGED' | 'TIME_BASED' | 'MANUAL';
43
+ triggerType: string;
22
44
  triggerFromStatusId: string | null;
23
45
  triggerToStatusId: string | null;
24
46
  triggerTimeDays: number | null;
@@ -30,12 +52,21 @@ interface Workflow {
30
52
 
31
53
  export default function AutomatisationPage() {
32
54
  const router = useRouter();
55
+ const { confirm, ConfirmDialog } = useConfirm();
56
+ const { hasPermission } = useUserRole();
57
+ const toast = useAppToast();
58
+
59
+ // Permissions
60
+ const canCreate = hasPermission('workflows.create');
61
+ const canDelete = hasPermission('workflows.delete');
62
+
33
63
  const [workflows, setWorkflows] = useState<Workflow[]>([]);
34
64
  const [statuses, setStatuses] = useState<Status[]>([]);
35
65
  const [loading, setLoading] = useState(true);
36
66
  const [error, setError] = useState('');
37
67
  const [success, setSuccess] = useState('');
38
68
  const [search, setSearch] = useState('');
69
+ const [showGuide, setShowGuide] = useState(false);
39
70
 
40
71
  async function fetchWorkflows() {
41
72
  try {
@@ -48,7 +79,7 @@ export default function AutomatisationPage() {
48
79
  setWorkflows(data);
49
80
  } catch (err: any) {
50
81
  console.error('Erreur lors du chargement des workflows:', err);
51
- setError(err.message || 'Erreur lors du chargement des workflows');
82
+ setError(devToast('Erreur lors du chargement des workflows', err));
52
83
  } finally {
53
84
  setLoading(false);
54
85
  }
@@ -68,11 +99,31 @@ export default function AutomatisationPage() {
68
99
  useEffect(() => {
69
100
  fetchWorkflows();
70
101
  fetchStatuses();
71
- // eslint-disable-next-line react-hooks/exhaustive-deps
102
+
72
103
  }, []);
73
104
 
105
+ useEffect(() => {
106
+ if (!success) return;
107
+ toast.success(success);
108
+ setSuccess('');
109
+ }, [success, toast]);
110
+
111
+ useEffect(() => {
112
+ if (!error) return;
113
+ toast.error(error);
114
+ setError('');
115
+ }, [error, toast]);
116
+
74
117
  const handleDelete = async (id: string) => {
75
- if (!confirm('Voulez-vous vraiment supprimer ce workflow ?')) {
118
+ const confirmed = await confirm({
119
+ title: 'Supprimer le workflow',
120
+ description: 'Voulez-vous vraiment supprimer ce workflow ? Cette action est irréversible.',
121
+ confirmText: 'Supprimer',
122
+ cancelText: 'Annuler',
123
+ variant: 'destructive',
124
+ });
125
+
126
+ if (!confirmed) {
76
127
  return;
77
128
  }
78
129
 
@@ -89,7 +140,7 @@ export default function AutomatisationPage() {
89
140
  fetchWorkflows();
90
141
  setTimeout(() => setSuccess(''), 5000);
91
142
  } catch (err: any) {
92
- setError(err.message || 'Erreur lors de la suppression');
143
+ setError(devToast('Erreur lors de la suppression du workflow', err));
93
144
  }
94
145
  };
95
146
 
@@ -112,12 +163,13 @@ export default function AutomatisationPage() {
112
163
  throw new Error('Erreur lors de la mise à jour');
113
164
  }
114
165
 
166
+ toast.success(workflow.active ? 'Workflow désactivé' : 'Workflow activé');
115
167
  fetchWorkflows();
116
168
  } catch (err: any) {
117
- setError(err.message || 'Erreur lors de la mise à jour');
169
+ setError(devToast('Erreur lors de la mise à jour du workflow', err));
118
170
  }
119
171
  };
120
- const getTriggerLabel = (triggerType: Workflow['triggerType']) => {
172
+ const getTriggerLabel = (triggerType: string) => {
121
173
  switch (triggerType) {
122
174
  case 'CONTACT_CREATED':
123
175
  return 'Nouveau contact créé';
@@ -127,8 +179,12 @@ export default function AutomatisationPage() {
127
179
  return 'Basé sur le temps';
128
180
  case 'MANUAL':
129
181
  return 'Déclencheur manuel';
182
+ case 'TASK_COMPLETED':
183
+ return 'Tâche complétée';
184
+ case 'CONTACT_ASSIGNMENT_CHANGED':
185
+ return "Changement d'assignation";
130
186
  default:
131
- return '';
187
+ return triggerType;
132
188
  }
133
189
  };
134
190
 
@@ -142,196 +198,417 @@ export default function AutomatisationPage() {
142
198
  });
143
199
 
144
200
  return (
145
- <div className="h-full">
146
- <PageHeader
147
- title="Automatisation"
148
- description="Créez et gérez vos workflows d'automatisation"
149
- action={
150
- <button
151
- onClick={() => router.push('/automatisation/new')}
152
- className="inline-flex cursor-pointer items-center gap-2 rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-indigo-700 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:outline-none"
153
- >
154
- <Plus className="h-4 w-4" />
155
- Nouveau workflow
156
- </button>
157
- }
158
- />
159
-
160
- <div className="p-4 sm:p-6 lg:p-8">
161
- {success && (
162
- <div className="mb-4 rounded-lg border border-green-200 bg-green-50 p-4">
163
- <p className="text-sm font-medium text-green-800">{success}</p>
164
- </div>
165
- )}
201
+ <ProtectedPage requiredPermission="workflows.view">
202
+ <div className="h-full">
203
+ <PageHeader
204
+ title="Automatisation"
205
+ description="Créez et gérez vos workflows d'automatisation"
206
+ action={
207
+ canCreate ? (
208
+ <button
209
+ onClick={() => router.push('/automatisation/new')}
210
+ className="inline-flex cursor-pointer items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-blue-700 focus-visible:ring-2 focus-visible:ring-gray-400/30 focus-visible:ring-offset-2 focus-visible:outline-none"
211
+ >
212
+ <Plus className="h-4 w-4" />
213
+ Nouveau workflow
214
+ </button>
215
+ ) : undefined
216
+ }
217
+ />
166
218
 
167
- {error && (
168
- <div className="mb-4 rounded-lg border border-red-200 bg-red-50 p-4">
169
- <p className="text-sm font-medium text-red-800">{error}</p>
170
- </div>
171
- )}
172
-
173
- {loading ? (
174
- <div className="rounded-xl bg-white p-8 text-center shadow-sm">
175
- <p className="text-sm text-gray-500">Chargement des workflows...</p>
176
- </div>
177
- ) : workflows.length === 0 ? (
178
- <div className="rounded-xl bg-white p-12 text-center shadow-sm">
179
- <p className="text-gray-500">Aucun workflow créé pour le moment.</p>
219
+ <div className="p-4 sm:p-6 lg:p-8">
220
+ {/* Guide d'utilisation */}
221
+ <div className="mb-6 rounded-2xl border border-blue-100 bg-white shadow-sm">
180
222
  <button
181
- onClick={() => router.push('/automatisation/new')}
182
- className="mt-4 inline-flex cursor-pointer items-center gap-2 rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-indigo-700"
223
+ type="button"
224
+ onClick={() => setShowGuide((prev) => !prev)}
225
+ className="flex w-full cursor-pointer items-center justify-between px-5 py-4"
183
226
  >
184
- <Plus className="h-4 w-4" />
185
- Créer votre premier workflow
186
- </button>
187
- </div>
188
- ) : (
189
- <div className="rounded-xl border border-gray-200 bg-white shadow-sm">
190
- <div className="flex flex-col gap-3 border-b border-gray-100 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-6">
191
- <div className="flex items-center gap-2 text-sm text-gray-500">
192
- <span className="font-medium text-gray-900">Automatisations</span>
193
- <span className="rounded-full bg-gray-100 px-2 py-0.5 text-xs">
194
- {workflows.length}
195
- </span>
196
- </div>
197
- <div className="relative w-full max-w-xs">
198
- <Search className="pointer-events-none absolute top-2.5 left-3 h-4 w-4 text-gray-400" />
199
- <input
200
- type="text"
201
- value={search}
202
- onChange={(e) => setSearch(e.target.value)}
203
- placeholder="Rechercher une automatisation..."
204
- className="w-full rounded-full border border-gray-200 bg-gray-50 py-2 pr-3 pl-9 text-sm text-gray-900 placeholder:text-gray-400 focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-500/30 focus:outline-none"
205
- />
227
+ <div className="flex items-center gap-3">
228
+ <div className="flex h-9 w-9 items-center justify-center rounded-xl bg-blue-50">
229
+ <Info className="h-5 w-5 text-blue-600" />
230
+ </div>
231
+ <div className="text-left">
232
+ <p className="text-sm font-semibold text-gray-900">
233
+ Comment fonctionnent les automatisations ?
234
+ </p>
235
+ <p className="mt-0.5 text-xs text-gray-500">
236
+ Déclencheurs, actions, conditions -- tout comprendre en un coup d&apos;oeil
237
+ </p>
238
+ </div>
206
239
  </div>
207
- </div>
240
+ {showGuide ? (
241
+ <ChevronUp className="h-5 w-5 text-gray-400" />
242
+ ) : (
243
+ <ChevronDown className="h-5 w-5 text-gray-400" />
244
+ )}
245
+ </button>
208
246
 
209
- <div className="overflow-x-auto">
210
- <table className="min-w-full divide-y divide-gray-100 text-sm">
211
- <thead className="bg-gray-50/60">
212
- <tr>
213
- <th className="px-6 py-3 text-left text-xs font-medium tracking-wide text-gray-500 uppercase">
214
- Nom
215
- </th>
216
- <th className="px-6 py-3 text-left text-xs font-medium tracking-wide text-gray-500 uppercase">
217
- Déclencheur
218
- </th>
219
- <th className="px-6 py-3 text-left text-xs font-medium tracking-wide text-gray-500 uppercase">
220
- Actions
221
- </th>
222
- <th className="px-6 py-3 text-left text-xs font-medium tracking-wide text-gray-500 uppercase">
223
- Créé le
224
- </th>
225
- <th className="px-6 py-3 text-center text-xs font-medium tracking-wide text-gray-500 uppercase">
226
- Statut
227
- </th>
228
- <th className="px-4 py-3 text-right text-xs font-medium tracking-wide text-gray-500 uppercase">
229
- Actions
230
- </th>
231
- </tr>
232
- </thead>
233
- <tbody className="divide-y divide-gray-100">
234
- {filteredWorkflows.map((workflow) => (
235
- <tr
236
- key={workflow.id}
237
- className="group cursor-pointer transition-colors hover:bg-indigo-50/40"
238
- onClick={() => router.push(`/automatisation/${workflow.id}`)}
239
- >
240
- <td className="px-6 py-4 whitespace-nowrap">
241
- <div className="flex flex-col">
242
- <span className="font-medium text-gray-900">{workflow.name}</span>
243
- {workflow.description && (
244
- <span className="mt-0.5 line-clamp-1 text-xs text-gray-500">
245
- {workflow.description}
246
- </span>
247
- )}
247
+ {showGuide && (
248
+ <div className="border-t border-blue-50 px-5 pt-4 pb-6">
249
+ {/* Principe */}
250
+ <p className="text-sm leading-relaxed text-gray-600">
251
+ Un workflow suit un schéma simple : un <strong>déclencheur</strong> lance la
252
+ séquence, puis une ou plusieurs <strong>actions</strong> s&apos;exécutent dans
253
+ l&apos;ordre. Chaque action peut avoir un <strong>délai</strong> (jours / heures)
254
+ et une <strong>condition</strong> optionnelle pour s&apos;exécuter uniquement si
255
+ le contact remplit certains critères.
256
+ </p>
257
+
258
+ {/* Déclencheurs */}
259
+ <div className="mt-5">
260
+ <h3 className="flex items-center gap-2 text-xs font-semibold tracking-wide text-blue-600 uppercase">
261
+ <Zap className="h-4 w-4" />
262
+ Déclencheurs disponibles
263
+ </h3>
264
+ <div className="mt-3 grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
265
+ {[
266
+ {
267
+ icon: <UserPlus className="h-4 w-4 text-emerald-600" />,
268
+ title: 'Contact créé',
269
+ desc: "Se lance dès qu'un nouveau contact est ajouté au CRM.",
270
+ },
271
+ {
272
+ icon: <Tag className="h-4 w-4 text-blue-600" />,
273
+ title: 'Changement de statut',
274
+ desc: 'Se lance quand le statut du contact change (ex. Nouveau \u2192 Qualifié). Filtrable par statut source/cible.',
275
+ },
276
+ {
277
+ icon: <Clock className="h-4 w-4 text-blue-600" />,
278
+ title: 'Basé sur le temps',
279
+ desc: 'Se lance X jours/heures après un événement (création, dernier changement de statut, dernière interaction).',
280
+ },
281
+ {
282
+ icon: <CheckSquare className="h-4 w-4 text-violet-600" />,
283
+ title: 'Tâche complétée',
284
+ desc: 'Se lance quand une tâche liée à un contact est terminée. Filtrable par type de tâche.',
285
+ },
286
+ {
287
+ icon: <Users className="h-4 w-4 text-cyan-600" />,
288
+ title: "Changement d'assignation",
289
+ desc: 'Se lance quand le commercial ou le télépro assigné au contact change.',
290
+ },
291
+ {
292
+ icon: <Zap className="h-4 w-4 text-purple-600" />,
293
+ title: 'Manuel',
294
+ desc: "Se lance manuellement depuis la fiche d'un contact (bouton \u00ab Auto \u00bb).",
295
+ },
296
+ ].map((item) => (
297
+ <div
298
+ key={item.title}
299
+ className="rounded-xl border border-gray-100 bg-gray-50/50 p-3"
300
+ >
301
+ <div className="flex items-center gap-2">
302
+ {item.icon}
303
+ <p className="text-xs font-semibold text-gray-900">{item.title}</p>
248
304
  </div>
249
- </td>
250
- <td className="px-6 py-4 whitespace-nowrap text-gray-700">
305
+ <p className="mt-1.5 text-[11px] leading-relaxed text-gray-500">
306
+ {item.desc}
307
+ </p>
308
+ </div>
309
+ ))}
310
+ </div>
311
+ </div>
312
+
313
+ {/* Actions */}
314
+ <div className="mt-5">
315
+ <h3 className="flex items-center gap-2 text-xs font-semibold tracking-wide text-blue-600 uppercase">
316
+ <CheckSquare className="h-4 w-4" />
317
+ Actions possibles
318
+ </h3>
319
+ <div className="mt-3 grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
320
+ {[
321
+ {
322
+ icon: <Mail className="h-4 w-4 text-blue-500" />,
323
+ title: 'Envoyer un email',
324
+ desc: 'Envoie un email au contact en utilisant un template prédéfini. Les variables (prénom, nom, etc.) sont remplacées automatiquement.',
325
+ },
326
+ {
327
+ icon: <MessageSquare className="h-4 w-4 text-blue-500" />,
328
+ title: 'Envoyer un SMS',
329
+ desc: 'Envoie un SMS au numéro du contact avec un message personnalisable (variables supportées).',
330
+ },
331
+ {
332
+ icon: <Tag className="h-4 w-4 text-blue-500" />,
333
+ title: 'Changer le statut',
334
+ desc: 'Modifie automatiquement le statut du contact vers un statut de votre choix.',
335
+ },
336
+ {
337
+ icon: <CheckSquare className="h-4 w-4 text-blue-500" />,
338
+ title: 'Créer une tâche',
339
+ desc: 'Crée une tâche (appel, rendez-vous, etc.) avec type, priorité et assignation personnalisables.',
340
+ },
341
+ {
342
+ icon: <UserPlus className="h-4 w-4 text-blue-500" />,
343
+ title: 'Assigner le contact',
344
+ desc: 'Assigne ou réassigne le contact à un commercial et/ou un télépro de votre choix.',
345
+ },
346
+ {
347
+ icon: <StickyNote className="h-4 w-4 text-blue-500" />,
348
+ title: 'Ajouter une note',
349
+ desc: "Crée une note dans l'historique du contact. Supporte les variables de template.",
350
+ },
351
+ {
352
+ icon: <Bell className="h-4 w-4 text-blue-500" />,
353
+ title: 'Notifier un utilisateur',
354
+ desc: "Crée une tâche pour un utilisateur spécifique (ex. alerter un manager qu'un contact est qualifié).",
355
+ },
356
+ {
357
+ icon: <Clock className="h-4 w-4 text-blue-500" />,
358
+ title: 'Attendre',
359
+ desc: "Insère un délai dans la séquence avant l'action suivante (en jours et/ou heures).",
360
+ },
361
+ ].map((item) => (
362
+ <div
363
+ key={item.title}
364
+ className="rounded-xl border border-gray-100 bg-gray-50/50 p-3"
365
+ >
251
366
  <div className="flex items-center gap-2">
252
- <span className="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-700">
253
- {getTriggerLabel(workflow.triggerType)}
254
- </span>
255
- {workflow.triggerType === 'STATUS_CHANGED' && (
256
- <span className="text-xs text-gray-500">
257
- {statuses.find((s) => s.id === workflow.triggerFromStatusId)?.name ??
258
- 'Tous'}{' '}
259
- →{' '}
260
- {statuses.find((s) => s.id === workflow.triggerToStatusId)?.name ??
261
- 'Tous'}
262
- </span>
263
- )}
367
+ {item.icon}
368
+ <p className="text-xs font-semibold text-gray-900">{item.title}</p>
264
369
  </div>
265
- </td>
266
- <td className="px-6 py-4 whitespace-nowrap text-gray-700">
267
- <span className="inline-flex items-center rounded-full bg-indigo-50 px-2.5 py-0.5 text-xs font-medium text-indigo-700">
268
- {workflow.actions.length} action(s)
269
- </span>
270
- </td>
271
- <td className="px-6 py-4 text-xs whitespace-nowrap text-gray-500">
272
- {new Date(workflow.createdAt).toLocaleDateString()}
273
- </td>
274
- <td
275
- className="px-6 py-4 text-center whitespace-nowrap"
276
- onClick={(e) => {
277
- // Empêche la navigation vers la page de détail
278
- e.stopPropagation();
279
- }}
370
+ <p className="mt-1.5 text-[11px] leading-relaxed text-gray-500">
371
+ {item.desc}
372
+ </p>
373
+ </div>
374
+ ))}
375
+ </div>
376
+ </div>
377
+
378
+ {/* Conditions */}
379
+ <div className="mt-5">
380
+ <h3 className="flex items-center gap-2 text-xs font-semibold tracking-wide text-blue-600 uppercase">
381
+ <Filter className="h-4 w-4" />
382
+ Conditions sur les actions
383
+ </h3>
384
+ <p className="mt-2 text-xs leading-relaxed text-gray-600">
385
+ Chaque action peut être conditionnée pour ne s&apos;exécuter que si le contact
386
+ remplit certains critères au moment du traitement :
387
+ </p>
388
+ <ul className="mt-2 space-y-1.5 text-xs text-gray-600">
389
+ <li className="flex items-start gap-2">
390
+ <span className="mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full bg-blue-400" />
391
+ <span>
392
+ <strong>Statut du contact</strong> -- l&apos;action s&apos;exécute
393
+ uniquement si le statut du contact est (ou n&apos;est pas) égal à un statut
394
+ donné.
395
+ </span>
396
+ </li>
397
+ <li className="flex items-start gap-2">
398
+ <span className="mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full bg-blue-400" />
399
+ <span>
400
+ <strong>Origine du contact</strong> -- filtrer par origine (ex. &laquo; Site
401
+ web &raquo;, &laquo; Facebook &raquo;).
402
+ </span>
403
+ </li>
404
+ <li className="flex items-start gap-2">
405
+ <span className="mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full bg-blue-400" />
406
+ <span>
407
+ <strong>Société</strong> -- exécuter uniquement si le contact est (ou
408
+ n&apos;est pas) rattaché à une société.
409
+ </span>
410
+ </li>
411
+ </ul>
412
+ </div>
413
+
414
+ {/* Exemples */}
415
+ <div className="mt-5 rounded-xl border border-dashed border-blue-200 bg-blue-50/30 p-4">
416
+ <h3 className="text-xs font-semibold text-blue-700">
417
+ Exemples de workflows courants
418
+ </h3>
419
+ <ul className="mt-2 space-y-2 text-xs leading-relaxed text-blue-900/70">
420
+ <li>
421
+ <strong>Séquence de bienvenue :</strong> Déclencheur &laquo; Contact créé
422
+ &raquo; &rarr; Envoyer un email de bienvenue &rarr; Attendre 2 jours &rarr;
423
+ Créer une tâche d&apos;appel pour le commercial.
424
+ </li>
425
+ <li>
426
+ <strong>Relance automatique :</strong> Déclencheur &laquo; Basé sur le temps
427
+ &raquo; (7 jours sans interaction) &rarr; Envoyer un email de relance &rarr;
428
+ Ajouter une note dans l&apos;historique.
429
+ </li>
430
+ <li>
431
+ <strong>Qualification après RDV :</strong> Déclencheur &laquo; Changement de
432
+ statut &raquo; (ex. vers &laquo; Qualifié &raquo;) &rarr; Envoyer un email de
433
+ suivi &rarr; Notifier le manager.
434
+ </li>
435
+ <li>
436
+ <strong>Réassignation intelligente :</strong> Déclencheur &laquo; Tâche
437
+ complétée &raquo; (type Appel) &rarr; Si statut &ne; &laquo; Qualifié &raquo;,
438
+ assigner à un autre commercial.
439
+ </li>
440
+ </ul>
441
+ </div>
442
+ </div>
443
+ )}
444
+ </div>
445
+
446
+ {loading ? (
447
+ <PageLoader text="Chargement des workflows..." />
448
+ ) : workflows.length === 0 ? (
449
+ <div className="rounded-xl bg-white p-12 text-center shadow-sm">
450
+ <p className="text-gray-500">Aucun workflow créé pour le moment.</p>
451
+ {canCreate && (
452
+ <button
453
+ onClick={() => router.push('/automatisation/new')}
454
+ className="mt-4 inline-flex cursor-pointer items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-blue-700"
455
+ >
456
+ <Plus className="h-4 w-4" />
457
+ Créer votre premier workflow
458
+ </button>
459
+ )}
460
+ </div>
461
+ ) : (
462
+ <div className="rounded-xl border border-gray-200 bg-white shadow-sm">
463
+ <div className="flex flex-col gap-3 border-b border-gray-100 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-6">
464
+ <div className="flex items-center gap-2 text-sm text-gray-500">
465
+ <span className="font-medium text-gray-900">Automatisations</span>
466
+ <span className="rounded-full bg-gray-100 px-2 py-0.5 text-xs">
467
+ {workflows.length}
468
+ </span>
469
+ </div>
470
+ <div className="relative w-full max-w-xs">
471
+ <Search className="pointer-events-none absolute top-2.5 left-3 h-4 w-4 text-gray-400" />
472
+ <input
473
+ type="text"
474
+ value={search}
475
+ onChange={(e) => setSearch(e.target.value)}
476
+ placeholder="Rechercher une automatisation..."
477
+ className="w-full rounded-full border border-gray-200 bg-gray-50 py-2 pr-3 pl-9 text-sm text-gray-900 placeholder:text-gray-400 focus:bg-white focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
478
+ />
479
+ </div>
480
+ </div>
481
+
482
+ <div className="overflow-x-auto">
483
+ <table className="min-w-full divide-y divide-gray-100 text-sm">
484
+ <thead className="bg-gray-50/60">
485
+ <tr>
486
+ <th className="px-6 py-3 text-left text-xs font-medium tracking-wide text-gray-500 uppercase">
487
+ Nom
488
+ </th>
489
+ <th className="px-6 py-3 text-left text-xs font-medium tracking-wide text-gray-500 uppercase">
490
+ Déclencheur
491
+ </th>
492
+ <th className="px-6 py-3 text-left text-xs font-medium tracking-wide text-gray-500 uppercase">
493
+ Actions
494
+ </th>
495
+ <th className="px-6 py-3 text-left text-xs font-medium tracking-wide text-gray-500 uppercase">
496
+ Créé le
497
+ </th>
498
+ <th className="px-6 py-3 text-center text-xs font-medium tracking-wide text-gray-500 uppercase">
499
+ Statut
500
+ </th>
501
+ <th className="px-4 py-3 text-right text-xs font-medium tracking-wide text-gray-500 uppercase">
502
+ Actions
503
+ </th>
504
+ </tr>
505
+ </thead>
506
+ <tbody className="divide-y divide-gray-100">
507
+ {filteredWorkflows.map((workflow) => (
508
+ <tr
509
+ key={workflow.id}
510
+ className="group cursor-pointer transition-colors hover:bg-blue-50/40"
511
+ onClick={() => router.push(`/automatisation/${workflow.id}`)}
280
512
  >
281
- <div className="flex items-center justify-center gap-2">
282
- <button
283
- type="button"
284
- aria-label={
285
- workflow.active ? 'Désactiver le workflow' : 'Activer le workflow'
286
- }
287
- onClick={() => handleToggleActive(workflow)}
288
- className={cn(
289
- 'relative inline-flex h-5 w-9 cursor-pointer items-center rounded-full border transition-colors',
290
- workflow.active
291
- ? 'border-green-500 bg-green-500'
292
- : 'border-gray-300 bg-gray-200',
513
+ <td className="px-6 py-4 whitespace-nowrap">
514
+ <div className="flex flex-col">
515
+ <span className="font-medium text-gray-900">{workflow.name}</span>
516
+ {workflow.description && (
517
+ <span className="mt-0.5 line-clamp-1 text-xs text-gray-500">
518
+ {workflow.description}
519
+ </span>
520
+ )}
521
+ </div>
522
+ </td>
523
+ <td className="px-6 py-4 whitespace-nowrap text-gray-700">
524
+ <div className="flex items-center gap-2">
525
+ <span className="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-700">
526
+ {getTriggerLabel(workflow.triggerType)}
527
+ </span>
528
+ {workflow.triggerType === 'STATUS_CHANGED' && (
529
+ <span className="text-xs text-gray-500">
530
+ {statuses.find((s) => s.id === workflow.triggerFromStatusId)
531
+ ?.name ?? 'Tous'}{' '}
532
+ →{' '}
533
+ {statuses.find((s) => s.id === workflow.triggerToStatusId)?.name ??
534
+ 'Tous'}
535
+ </span>
293
536
  )}
294
- >
537
+ </div>
538
+ </td>
539
+ <td className="px-6 py-4 whitespace-nowrap text-gray-700">
540
+ <span className="inline-flex items-center rounded-full bg-blue-50 px-2.5 py-0.5 text-xs font-medium text-blue-700">
541
+ {workflow.actions.length} action(s)
542
+ </span>
543
+ </td>
544
+ <td className="px-6 py-4 text-xs whitespace-nowrap text-gray-500">
545
+ {new Date(workflow.createdAt).toLocaleDateString()}
546
+ </td>
547
+ <td
548
+ className="px-6 py-4 text-center whitespace-nowrap"
549
+ onClick={(e) => {
550
+ // Empêche la navigation vers la page de détail
551
+ e.stopPropagation();
552
+ }}
553
+ >
554
+ <div className="flex items-center justify-center gap-2">
555
+ <button
556
+ type="button"
557
+ aria-label={
558
+ workflow.active ? 'Désactiver le workflow' : 'Activer le workflow'
559
+ }
560
+ onClick={() => handleToggleActive(workflow)}
561
+ className={cn(
562
+ 'relative inline-flex h-5 w-9 cursor-pointer items-center rounded-full border transition-colors',
563
+ workflow.active
564
+ ? 'border-green-500 bg-green-500'
565
+ : 'border-gray-300 bg-gray-200',
566
+ )}
567
+ >
568
+ <span
569
+ className={cn(
570
+ 'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
571
+ workflow.active ? 'translate-x-4' : 'translate-x-1',
572
+ )}
573
+ />
574
+ </button>
295
575
  <span
296
576
  className={cn(
297
- 'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
298
- workflow.active ? 'translate-x-4' : 'translate-x-1',
577
+ 'text-xs font-medium',
578
+ workflow.active ? 'text-green-600' : 'text-gray-500',
299
579
  )}
300
- />
301
- </button>
302
- <span
303
- className={cn(
304
- 'text-xs font-medium',
305
- workflow.active ? 'text-green-600' : 'text-gray-500',
580
+ >
581
+ {workflow.active ? 'Actif' : 'Inactif'}
582
+ </span>
583
+ </div>
584
+ </td>
585
+ <td
586
+ className="px-4 py-4 text-right whitespace-nowrap"
587
+ onClick={(e) => e.stopPropagation()}
588
+ >
589
+ <div className="flex items-center justify-end gap-1">
590
+ {canDelete && (
591
+ <button
592
+ type="button"
593
+ onClick={() => handleDelete(workflow.id)}
594
+ className="inline-flex cursor-pointer items-center rounded-lg px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600"
595
+ >
596
+ <Trash2 className="mr-1 h-4 w-4" />
597
+ Supprimer
598
+ </button>
306
599
  )}
307
- >
308
- {workflow.active ? 'Actif' : 'Inactif'}
309
- </span>
310
- </div>
311
- </td>
312
- <td
313
- className="px-4 py-4 text-right whitespace-nowrap"
314
- onClick={(e) => e.stopPropagation()}
315
- >
316
- <div className="flex items-center justify-end gap-1">
317
- <button
318
- type="button"
319
- onClick={() => handleDelete(workflow.id)}
320
- className="inline-flex cursor-pointer items-center rounded-lg px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600"
321
- >
322
- <Trash2 className="mr-1 h-4 w-4" />
323
- Supprimer
324
- </button>
325
- </div>
326
- </td>
327
- </tr>
328
- ))}
329
- </tbody>
330
- </table>
600
+ </div>
601
+ </td>
602
+ </tr>
603
+ ))}
604
+ </tbody>
605
+ </table>
606
+ </div>
331
607
  </div>
332
- </div>
333
- )}
608
+ )}
609
+ </div>
334
610
  </div>
335
- </div>
611
+ <ConfirmDialog />
612
+ </ProtectedPage>
336
613
  );
337
614
  }