create-crm-tmp 1.1.3 → 2.0.0

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