create-crm-tmp 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.prettierignore +2 -0
- package/template/README.md +53 -67
- package/template/components.json +22 -0
- package/template/exemple-contacts.csv +54 -0
- package/template/next.config.ts +27 -1
- package/template/package.json +64 -27
- package/template/prisma/schema.prisma +821 -72
- package/template/skills-lock.json +25 -0
- package/template/src/app/(auth)/invite/[token]/page.tsx +21 -24
- package/template/src/app/(auth)/reset-password/complete/page.tsx +12 -21
- package/template/src/app/(auth)/reset-password/page.tsx +12 -8
- package/template/src/app/(auth)/reset-password/verify/page.tsx +12 -8
- package/template/src/app/(auth)/signin/page.tsx +20 -17
- package/template/src/app/(dashboard)/agenda/page.tsx +2231 -2188
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +680 -323
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
- package/template/src/app/(dashboard)/automatisation/page.tsx +473 -180
- package/template/src/app/(dashboard)/closing/page.tsx +500 -468
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +5035 -4126
- package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1703 -0
- package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +3776 -2064
- package/template/src/app/(dashboard)/dashboard/page.tsx +37 -519
- package/template/src/app/(dashboard)/error.tsx +37 -0
- package/template/src/app/(dashboard)/layout.tsx +1 -1
- package/template/src/app/(dashboard)/loading.tsx +5 -0
- package/template/src/app/(dashboard)/settings/loading.tsx +19 -0
- package/template/src/app/(dashboard)/settings/page.tsx +2685 -2489
- package/template/src/app/(dashboard)/templates/page.tsx +500 -300
- package/template/src/app/(dashboard)/users/list/page.tsx +356 -350
- package/template/src/app/(dashboard)/users/page.tsx +279 -310
- package/template/src/app/(dashboard)/users/permissions/page.tsx +104 -99
- package/template/src/app/(dashboard)/users/roles/page.tsx +164 -137
- package/template/src/app/api/audit-logs/route.ts +1 -1
- package/template/src/app/api/auth/google/callback/route.ts +8 -5
- package/template/src/app/api/auth/google/disconnect/route.ts +2 -2
- package/template/src/app/api/companies/[id]/activities/route.ts +131 -0
- package/template/src/app/api/companies/[id]/route.ts +195 -0
- package/template/src/app/api/companies/export/route.ts +206 -0
- package/template/src/app/api/companies/route.ts +166 -0
- package/template/src/app/api/contact-views/[id]/pin/route.ts +69 -0
- package/template/src/app/api/contact-views/[id]/route.ts +197 -0
- package/template/src/app/api/contact-views/route.ts +146 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +77 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +7 -17
- package/template/src/app/api/contacts/[id]/files/route.ts +83 -44
- package/template/src/app/api/contacts/[id]/interactions/route.ts +37 -0
- package/template/src/app/api/contacts/[id]/kyc/route.ts +71 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +38 -29
- package/template/src/app/api/contacts/[id]/route.ts +111 -20
- package/template/src/app/api/contacts/[id]/send-email/route.ts +6 -0
- package/template/src/app/api/contacts/[id]/workflows/run/route.ts +61 -0
- package/template/src/app/api/contacts/export/route.ts +12 -17
- package/template/src/app/api/contacts/import/route.ts +22 -19
- package/template/src/app/api/contacts/import-preview/route.ts +139 -0
- package/template/src/app/api/contacts/route.ts +202 -49
- package/template/src/app/api/dashboard/stats/route.ts +9 -292
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +203 -185
- package/template/src/app/api/invite/complete/route.ts +20 -23
- package/template/src/app/api/reminders/route.ts +1 -0
- package/template/src/app/api/reset-password/complete/route.ts +11 -13
- package/template/src/app/api/send/route.ts +9 -85
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +10 -21
- package/template/src/app/api/settings/closing-reasons/route.ts +10 -21
- package/template/src/app/api/settings/company/route.ts +19 -26
- package/template/src/app/api/settings/google-ads/[id]/route.ts +20 -23
- package/template/src/app/api/settings/google-ads/route.ts +20 -23
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +20 -23
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +23 -32
- package/template/src/app/api/settings/google-sheet/preview/route.ts +104 -0
- package/template/src/app/api/settings/google-sheet/route.ts +20 -23
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -23
- package/template/src/app/api/settings/meta-leads/route.ts +20 -23
- package/template/src/app/api/settings/statuses/[id]/route.ts +33 -23
- package/template/src/app/api/settings/statuses/route.ts +24 -22
- package/template/src/app/api/statuses/route.ts +2 -5
- package/template/src/app/api/tasks/[id]/attendees/route.ts +14 -7
- package/template/src/app/api/tasks/[id]/route.ts +161 -137
- package/template/src/app/api/tasks/meet/route.ts +11 -8
- package/template/src/app/api/tasks/route.ts +155 -95
- package/template/src/app/api/templates/[id]/route.ts +22 -13
- package/template/src/app/api/templates/route.ts +22 -5
- package/template/src/app/api/users/[id]/resend-invite/route.ts +95 -0
- package/template/src/app/api/users/[id]/route.ts +16 -1
- package/template/src/app/api/users/commercials/route.ts +38 -0
- package/template/src/app/api/users/for-agenda/route.ts +1 -2
- package/template/src/app/api/users/route.ts +94 -55
- package/template/src/app/api/webhooks/google-ads/route.ts +20 -1
- package/template/src/app/api/webhooks/meta-leads/route.ts +18 -1
- package/template/src/app/api/workflows/[id]/route.ts +33 -6
- package/template/src/app/api/workflows/process/route.ts +509 -146
- package/template/src/app/api/workflows/route.ts +46 -4
- package/template/src/app/globals.css +210 -101
- package/template/src/app/layout.tsx +19 -8
- package/template/src/app/page.tsx +37 -7
- package/template/src/components/address-autocomplete.tsx +232 -0
- package/template/src/components/contacts/filter-bar.tsx +181 -0
- package/template/src/components/contacts/filter-builder.tsx +589 -0
- package/template/src/components/contacts/save-view-dialog.tsx +160 -0
- package/template/src/components/contacts/views-tab-bar.tsx +440 -0
- package/template/src/components/dashboard/activity-chart.tsx +31 -39
- package/template/src/components/dashboard/dashboard-content.tsx +79 -0
- package/template/src/components/dashboard/stat-card.tsx +40 -42
- package/template/src/components/dashboard/tasks-pie-chart.tsx +34 -37
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +78 -72
- package/template/src/components/date-picker.tsx +396 -0
- package/template/src/components/editor.tsx +27 -13
- package/template/src/components/email-template.tsx +4 -2
- package/template/src/components/global-search.tsx +358 -0
- package/template/src/components/header.tsx +57 -62
- package/template/src/components/invitation-email-template.tsx +4 -2
- package/template/src/components/lazy-editor.tsx +11 -0
- package/template/src/components/meet-cancellation-email-template.tsx +11 -3
- package/template/src/components/meet-confirmation-email-template.tsx +10 -3
- package/template/src/components/meet-update-email-template.tsx +10 -3
- package/template/src/components/page-header.tsx +19 -15
- package/template/src/components/protected-page.tsx +94 -0
- package/template/src/components/reset-password-email-template.tsx +4 -2
- package/template/src/components/sidebar.tsx +92 -94
- package/template/src/components/skeleton.tsx +128 -42
- package/template/src/components/ui/accordion.tsx +64 -0
- package/template/src/components/ui/alert-dialog.tsx +139 -0
- package/template/src/components/ui/button.tsx +60 -0
- package/template/src/components/view-as-banner.tsx +1 -1
- package/template/src/components/view-as-modal.tsx +21 -16
- package/template/src/config/nav-pages.ts +108 -0
- package/template/src/contexts/app-toast-context.tsx +174 -0
- package/template/src/contexts/sidebar-context.tsx +16 -47
- package/template/src/contexts/task-reminder-context.tsx +6 -6
- package/template/src/contexts/view-as-context.tsx +11 -16
- package/template/src/hooks/use-alert.tsx +65 -0
- package/template/src/hooks/use-confirm.tsx +87 -0
- package/template/src/hooks/use-contact-views.ts +140 -0
- package/template/src/hooks/use-contacts.ts +69 -0
- package/template/src/hooks/use-fetch.ts +17 -0
- package/template/src/hooks/use-focus-trap.ts +73 -0
- package/template/src/hooks/use-statuses.ts +22 -0
- package/template/src/lib/address-api.ts +155 -0
- package/template/src/lib/cache.ts +73 -0
- package/template/src/lib/check-permission.ts +12 -177
- package/template/src/lib/contact-interactions.ts +3 -1
- package/template/src/lib/contact-view-filters.ts +341 -0
- package/template/src/lib/dashboard-stats.ts +224 -0
- package/template/src/lib/date-utils.ts +49 -0
- package/template/src/lib/get-auth-user.ts +25 -0
- package/template/src/lib/google-calendar.ts +54 -12
- package/template/src/lib/google-drive.ts +796 -75
- package/template/src/lib/google-fetch.ts +63 -0
- package/template/src/lib/local-storage.ts +34 -0
- package/template/src/lib/permissions.ts +245 -47
- package/template/src/lib/prisma.ts +11 -11
- package/template/src/lib/roles.ts +14 -39
- package/template/src/lib/template-variables.ts +67 -33
- package/template/src/lib/utils.ts +26 -2
- package/template/src/lib/workflow-executor.ts +445 -229
- package/template/src/proxy.ts +34 -73
- package/template/src/types/contact-views.ts +351 -0
- package/template/src/types/yousign.ts +52 -0
- package/template/vercel.json +12 -0
- package/template/WORKFLOWS_CRON.md +0 -185
- package/template/prisma/migrations/20251126144728_init/migration.sql +0 -78
- package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +0 -5
- package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +0 -19
- package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +0 -22
- package/template/prisma/migrations/20251128132303_add_status/migration.sql +0 -23
- package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +0 -75
- package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +0 -2
- package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +0 -45
- package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +0 -2
- package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +0 -27
- package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +0 -20
- package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +0 -18
- package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +0 -32
- package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +0 -20
- package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +0 -12
- package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +0 -21
- package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +0 -11
- package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +0 -12
- package/template/prisma/migrations/20251208094843_mg/migration.sql +0 -14
- package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +0 -14
- package/template/prisma/migrations/20251208110000_add_templates/migration.sql +0 -26
- package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +0 -2
- package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +0 -2
- package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +0 -2
- package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +0 -3
- package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +0 -21
- package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +0 -2
- package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +0 -10
- package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +0 -26
- package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +0 -24
- package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +0 -11
- package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +0 -12
- package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +0 -25
- package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +0 -8
- package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +0 -2
- package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +0 -80
- package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +0 -32
- package/template/prisma/migrations/20251220000000_add_task_interaction_type/migration.sql +0 -4
- package/template/prisma/migrations/20251221000000_add_task_type/migration.sql +0 -3
- package/template/prisma/migrations/20251221000001_add_event_color/migration.sql +0 -23
- package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +0 -20
- package/template/prisma/migrations/migration_lock.toml +0 -3
- package/template/src/app/(dashboard)/users/layout.tsx +0 -30
- package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -47
- package/template/src/app/api/dashboard/widgets/route.ts +0 -181
- package/template/src/components/dashboard/add-widget-dialog.tsx +0 -161
- package/template/src/components/dashboard/color-picker.tsx +0 -65
- package/template/src/components/dashboard/contacts-chart.tsx +0 -69
- package/template/src/components/dashboard/interactions-by-type-chart.tsx +0 -121
- package/template/src/components/dashboard/recent-activity.tsx +0 -157
- package/template/src/components/dashboard/sales-analytics-chart.tsx +0 -77
- package/template/src/components/dashboard/status-distribution-chart.tsx +0 -82
- package/template/src/components/dashboard/top-contacts-list.tsx +0 -119
- package/template/src/components/dashboard/widget-wrapper.tsx +0 -39
- package/template/src/contexts/dashboard-theme-context.tsx +0 -58
- package/template/src/lib/dashboard-themes.ts +0 -140
- package/template/src/lib/default-widgets.ts +0 -14
- package/template/src/lib/widget-registry.ts +0 -177
|
@@ -2,8 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Plus,
|
|
7
|
+
Trash2,
|
|
8
|
+
Mail,
|
|
9
|
+
MessageSquare,
|
|
10
|
+
Tag,
|
|
11
|
+
CheckSquare,
|
|
12
|
+
Clock,
|
|
13
|
+
UserPlus,
|
|
14
|
+
StickyNote,
|
|
15
|
+
Bell,
|
|
16
|
+
} from 'lucide-react';
|
|
6
17
|
import { cn } from '@/lib/utils';
|
|
18
|
+
import { useAppToast } from '@/contexts/app-toast-context';
|
|
7
19
|
|
|
8
20
|
interface Status {
|
|
9
21
|
id: string;
|
|
@@ -17,9 +29,35 @@ interface Template {
|
|
|
17
29
|
type: 'EMAIL' | 'SMS' | 'NOTE';
|
|
18
30
|
}
|
|
19
31
|
|
|
32
|
+
interface UserOption {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
role: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ActionType =
|
|
39
|
+
| 'SEND_EMAIL'
|
|
40
|
+
| 'SEND_SMS'
|
|
41
|
+
| 'CHANGE_STATUS'
|
|
42
|
+
| 'CREATE_TASK'
|
|
43
|
+
| 'WAIT'
|
|
44
|
+
| 'ASSIGN_CONTACT'
|
|
45
|
+
| 'ADD_NOTE'
|
|
46
|
+
| 'NOTIFY_USER';
|
|
47
|
+
|
|
48
|
+
type TriggerType =
|
|
49
|
+
| 'CONTACT_CREATED'
|
|
50
|
+
| 'STATUS_CHANGED'
|
|
51
|
+
| 'TIME_BASED'
|
|
52
|
+
| 'MANUAL'
|
|
53
|
+
| 'TASK_COMPLETED'
|
|
54
|
+
| 'TRANSACTION_CREATED'
|
|
55
|
+
| 'TRANSACTION_STATUS_CHANGED'
|
|
56
|
+
| 'CONTACT_ASSIGNMENT_CHANGED';
|
|
57
|
+
|
|
20
58
|
export interface WorkflowAction {
|
|
21
59
|
id?: string;
|
|
22
|
-
actionType:
|
|
60
|
+
actionType: ActionType;
|
|
23
61
|
order: number;
|
|
24
62
|
delayDays: number;
|
|
25
63
|
delayHours: number;
|
|
@@ -28,8 +66,17 @@ export interface WorkflowAction {
|
|
|
28
66
|
newStatusId?: string | null;
|
|
29
67
|
taskTitle?: string | null;
|
|
30
68
|
taskDescription?: string | null;
|
|
69
|
+
taskType?: string | null;
|
|
70
|
+
taskPriority?: string | null;
|
|
71
|
+
taskAssignedUserId?: string | null;
|
|
72
|
+
assignCommercialId?: string | null;
|
|
73
|
+
assignTeleproId?: string | null;
|
|
74
|
+
noteContent?: string | null;
|
|
75
|
+
notifyUserId?: string | null;
|
|
31
76
|
conditionOperator?: 'EQUALS' | 'NOT_EQUALS' | null;
|
|
32
77
|
conditionStatusId?: string | null;
|
|
78
|
+
conditionOrigin?: string | null;
|
|
79
|
+
conditionHasCompany?: boolean | null;
|
|
33
80
|
}
|
|
34
81
|
|
|
35
82
|
export interface WorkflowEditorInitialData {
|
|
@@ -37,11 +84,15 @@ export interface WorkflowEditorInitialData {
|
|
|
37
84
|
name: string;
|
|
38
85
|
description: string | null;
|
|
39
86
|
active: boolean;
|
|
40
|
-
triggerType:
|
|
87
|
+
triggerType: TriggerType;
|
|
41
88
|
triggerFromStatusId: string | null;
|
|
42
89
|
triggerToStatusId: string | null;
|
|
43
90
|
triggerTimeDays: number | null;
|
|
44
91
|
triggerTimeHours: number | null;
|
|
92
|
+
triggerTimeReference: string | null;
|
|
93
|
+
triggerTaskType: string | null;
|
|
94
|
+
triggerTransactionFromStatus: string | null;
|
|
95
|
+
triggerTransactionToStatus: string | null;
|
|
45
96
|
actions: WorkflowAction[];
|
|
46
97
|
}
|
|
47
98
|
|
|
@@ -49,8 +100,48 @@ interface WorkflowEditorProps {
|
|
|
49
100
|
workflowId?: string;
|
|
50
101
|
}
|
|
51
102
|
|
|
103
|
+
const TRANSACTION_STATUSES = [
|
|
104
|
+
{ value: 'DRAFT', label: 'Brouillon' },
|
|
105
|
+
{ value: 'PENDING_ID_VERIFICATION', label: 'Vérification identité' },
|
|
106
|
+
{ value: 'ID_VERIFIED', label: 'Identité vérifiée' },
|
|
107
|
+
{ value: 'ITEMS_ENTERED', label: 'Articles saisis' },
|
|
108
|
+
{ value: 'PENDING_SIGNATURE', label: 'En attente de signature' },
|
|
109
|
+
{ value: 'SIGNED', label: 'Signée' },
|
|
110
|
+
{ value: 'LOCKED', label: 'Verrouillée' },
|
|
111
|
+
{ value: 'PAYMENT_PENDING', label: 'Paiement en attente' },
|
|
112
|
+
{ value: 'COMPLETED', label: 'Complétée' },
|
|
113
|
+
{ value: 'PENDING_RETRACTION', label: 'Rétractation en attente' },
|
|
114
|
+
{ value: 'CANCELLED_RETRACTION', label: 'Rétractation annulée' },
|
|
115
|
+
{ value: 'CANCELLED', label: 'Annulée' },
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const TASK_TYPES = [
|
|
119
|
+
{ value: '', label: 'Tous les types' },
|
|
120
|
+
{ value: 'CALL', label: 'Appel' },
|
|
121
|
+
{ value: 'MEETING', label: 'Rendez-vous' },
|
|
122
|
+
{ value: 'EMAIL', label: 'Email' },
|
|
123
|
+
{ value: 'VIDEO_CONFERENCE', label: 'Visioconférence' },
|
|
124
|
+
{ value: 'PHYSICAL_APPOINTMENT', label: 'RDV physique' },
|
|
125
|
+
{ value: 'OTHER', label: 'Autre' },
|
|
126
|
+
{ value: 'TASK', label: 'Tâche' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const TASK_PRIORITIES = [
|
|
130
|
+
{ value: 'LOW', label: 'Basse' },
|
|
131
|
+
{ value: 'MEDIUM', label: 'Moyenne' },
|
|
132
|
+
{ value: 'HIGH', label: 'Haute' },
|
|
133
|
+
{ value: 'URGENT', label: 'Urgente' },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const TIME_REFERENCES = [
|
|
137
|
+
{ value: 'CONTACT_CREATED_DATE', label: 'Création du contact' },
|
|
138
|
+
{ value: 'LAST_STATUS_CHANGE', label: 'Dernier changement de statut' },
|
|
139
|
+
{ value: 'LAST_INTERACTION', label: 'Dernière interaction' },
|
|
140
|
+
];
|
|
141
|
+
|
|
52
142
|
export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
53
143
|
const router = useRouter();
|
|
144
|
+
const toast = useAppToast();
|
|
54
145
|
|
|
55
146
|
const [loading, setLoading] = useState<boolean>(!!workflowId);
|
|
56
147
|
const [saving, setSaving] = useState(false);
|
|
@@ -59,6 +150,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
59
150
|
|
|
60
151
|
const [statuses, setStatuses] = useState<Status[]>([]);
|
|
61
152
|
const [emailTemplates, setEmailTemplates] = useState<Template[]>([]);
|
|
153
|
+
const [users, setUsers] = useState<UserOption[]>([]);
|
|
62
154
|
|
|
63
155
|
const [formData, setFormData] = useState<WorkflowEditorInitialData>({
|
|
64
156
|
id: undefined,
|
|
@@ -70,6 +162,10 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
70
162
|
triggerToStatusId: null,
|
|
71
163
|
triggerTimeDays: null,
|
|
72
164
|
triggerTimeHours: null,
|
|
165
|
+
triggerTimeReference: null,
|
|
166
|
+
triggerTaskType: null,
|
|
167
|
+
triggerTransactionFromStatus: null,
|
|
168
|
+
triggerTransactionToStatus: null,
|
|
73
169
|
actions: [],
|
|
74
170
|
});
|
|
75
171
|
|
|
@@ -80,12 +176,11 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
80
176
|
useEffect(() => {
|
|
81
177
|
fetchStatuses();
|
|
82
178
|
fetchEmailTemplates();
|
|
179
|
+
fetchUsers();
|
|
83
180
|
}, []);
|
|
84
181
|
|
|
85
182
|
useEffect(() => {
|
|
86
|
-
if (!workflowId)
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
183
|
+
if (!workflowId) return;
|
|
89
184
|
|
|
90
185
|
const fetchWorkflow = async () => {
|
|
91
186
|
try {
|
|
@@ -107,6 +202,10 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
107
202
|
triggerToStatusId: data.triggerToStatusId,
|
|
108
203
|
triggerTimeDays: data.triggerTimeDays,
|
|
109
204
|
triggerTimeHours: data.triggerTimeHours,
|
|
205
|
+
triggerTimeReference: data.triggerTimeReference,
|
|
206
|
+
triggerTaskType: data.triggerTaskType,
|
|
207
|
+
triggerTransactionFromStatus: data.triggerTransactionFromStatus,
|
|
208
|
+
triggerTransactionToStatus: data.triggerTransactionToStatus,
|
|
110
209
|
actions: data.actions,
|
|
111
210
|
});
|
|
112
211
|
setActions(data.actions || []);
|
|
@@ -120,7 +219,6 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
120
219
|
fetchWorkflow();
|
|
121
220
|
}, [workflowId]);
|
|
122
221
|
|
|
123
|
-
// Fermer le menu d'actions en cliquant en dehors
|
|
124
222
|
useEffect(() => {
|
|
125
223
|
const handleClickOutside = (event: MouseEvent) => {
|
|
126
224
|
if (actionMenuRef.current && !actionMenuRef.current.contains(event.target as Node)) {
|
|
@@ -137,13 +235,28 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
137
235
|
};
|
|
138
236
|
}, [showActionMenu]);
|
|
139
237
|
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (!error) return;
|
|
240
|
+
toast.error(error);
|
|
241
|
+
setError('');
|
|
242
|
+
}, [error, toast]);
|
|
243
|
+
|
|
244
|
+
// Workflow non trouvé → toast + redirection (géré par l'effet error ci-dessus)
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (!workflowId || loading) return;
|
|
247
|
+
if (error) router.replace('/automatisation');
|
|
248
|
+
}, [workflowId, loading, error, router]);
|
|
249
|
+
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (!success) return;
|
|
252
|
+
toast.success(success);
|
|
253
|
+
setSuccess('');
|
|
254
|
+
}, [success, toast]);
|
|
255
|
+
|
|
140
256
|
const fetchStatuses = async () => {
|
|
141
257
|
try {
|
|
142
258
|
const response = await fetch('/api/settings/statuses');
|
|
143
|
-
if (response.ok)
|
|
144
|
-
const data = await response.json();
|
|
145
|
-
setStatuses(data);
|
|
146
|
-
}
|
|
259
|
+
if (response.ok) setStatuses(await response.json());
|
|
147
260
|
} catch (err) {
|
|
148
261
|
console.error('Erreur lors du chargement des statuts:', err);
|
|
149
262
|
}
|
|
@@ -152,12 +265,21 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
152
265
|
const fetchEmailTemplates = async () => {
|
|
153
266
|
try {
|
|
154
267
|
const response = await fetch('/api/templates?type=EMAIL');
|
|
268
|
+
if (response.ok) setEmailTemplates(await response.json());
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.error('Erreur lors du chargement des templates:', err);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const fetchUsers = async () => {
|
|
275
|
+
try {
|
|
276
|
+
const response = await fetch('/api/users');
|
|
155
277
|
if (response.ok) {
|
|
156
278
|
const data = await response.json();
|
|
157
|
-
|
|
279
|
+
setUsers(data.map((u: any) => ({ id: u.id, name: u.name, role: u.role })));
|
|
158
280
|
}
|
|
159
281
|
} catch (err) {
|
|
160
|
-
console.error('Erreur lors du chargement des
|
|
282
|
+
console.error('Erreur lors du chargement des utilisateurs:', err);
|
|
161
283
|
}
|
|
162
284
|
};
|
|
163
285
|
|
|
@@ -189,6 +311,10 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
189
311
|
triggerToStatusId: formData.triggerToStatusId || null,
|
|
190
312
|
triggerTimeDays: formData.triggerTimeDays || null,
|
|
191
313
|
triggerTimeHours: formData.triggerTimeHours || null,
|
|
314
|
+
triggerTimeReference: formData.triggerTimeReference || null,
|
|
315
|
+
triggerTaskType: formData.triggerTaskType || null,
|
|
316
|
+
triggerTransactionFromStatus: formData.triggerTransactionFromStatus || null,
|
|
317
|
+
triggerTransactionToStatus: formData.triggerTransactionToStatus || null,
|
|
192
318
|
actions: actions.map((action, index) => ({
|
|
193
319
|
...action,
|
|
194
320
|
order: index,
|
|
@@ -213,7 +339,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
213
339
|
}
|
|
214
340
|
};
|
|
215
341
|
|
|
216
|
-
const addAction = (actionType:
|
|
342
|
+
const addAction = (actionType: ActionType) => {
|
|
217
343
|
const newAction: WorkflowAction = {
|
|
218
344
|
actionType,
|
|
219
345
|
order: actions.length,
|
|
@@ -221,6 +347,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
221
347
|
delayHours: 0,
|
|
222
348
|
};
|
|
223
349
|
setActions([...actions, newAction]);
|
|
350
|
+
setShowActionMenu(false);
|
|
224
351
|
};
|
|
225
352
|
|
|
226
353
|
const removeAction = (index: number) => {
|
|
@@ -233,24 +360,30 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
233
360
|
setActions(newActions);
|
|
234
361
|
};
|
|
235
362
|
|
|
236
|
-
const getActionIcon = (actionType:
|
|
363
|
+
const getActionIcon = (actionType: ActionType) => {
|
|
237
364
|
switch (actionType) {
|
|
238
365
|
case 'SEND_EMAIL':
|
|
239
|
-
return <Mail className="h-5 w-5 text-
|
|
366
|
+
return <Mail className="h-5 w-5 text-blue-600" />;
|
|
240
367
|
case 'SEND_SMS':
|
|
241
|
-
return <MessageSquare className="h-5 w-5 text-
|
|
368
|
+
return <MessageSquare className="h-5 w-5 text-blue-600" />;
|
|
242
369
|
case 'CHANGE_STATUS':
|
|
243
|
-
return <Tag className="h-5 w-5 text-
|
|
370
|
+
return <Tag className="h-5 w-5 text-blue-600" />;
|
|
244
371
|
case 'CREATE_TASK':
|
|
245
|
-
return <CheckSquare className="h-5 w-5 text-
|
|
372
|
+
return <CheckSquare className="h-5 w-5 text-blue-600" />;
|
|
246
373
|
case 'WAIT':
|
|
247
|
-
return <Clock className="h-5 w-5 text-
|
|
374
|
+
return <Clock className="h-5 w-5 text-blue-600" />;
|
|
375
|
+
case 'ASSIGN_CONTACT':
|
|
376
|
+
return <UserPlus className="h-5 w-5 text-blue-600" />;
|
|
377
|
+
case 'ADD_NOTE':
|
|
378
|
+
return <StickyNote className="h-5 w-5 text-blue-600" />;
|
|
379
|
+
case 'NOTIFY_USER':
|
|
380
|
+
return <Bell className="h-5 w-5 text-blue-600" />;
|
|
248
381
|
default:
|
|
249
382
|
return null;
|
|
250
383
|
}
|
|
251
384
|
};
|
|
252
385
|
|
|
253
|
-
const getActionLabel = (actionType:
|
|
386
|
+
const getActionLabel = (actionType: ActionType) => {
|
|
254
387
|
switch (actionType) {
|
|
255
388
|
case 'SEND_EMAIL':
|
|
256
389
|
return 'Envoyer un email';
|
|
@@ -262,12 +395,18 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
262
395
|
return 'Créer une tâche';
|
|
263
396
|
case 'WAIT':
|
|
264
397
|
return 'Attendre';
|
|
398
|
+
case 'ASSIGN_CONTACT':
|
|
399
|
+
return 'Assigner le contact';
|
|
400
|
+
case 'ADD_NOTE':
|
|
401
|
+
return 'Ajouter une note';
|
|
402
|
+
case 'NOTIFY_USER':
|
|
403
|
+
return 'Notifier un utilisateur';
|
|
265
404
|
default:
|
|
266
405
|
return '';
|
|
267
406
|
}
|
|
268
407
|
};
|
|
269
408
|
|
|
270
|
-
const getTriggerLabel = (triggerType:
|
|
409
|
+
const getTriggerLabel = (triggerType: TriggerType) => {
|
|
271
410
|
switch (triggerType) {
|
|
272
411
|
case 'CONTACT_CREATED':
|
|
273
412
|
return 'Nouveau contact créé';
|
|
@@ -277,35 +416,362 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
277
416
|
return 'Basé sur le temps';
|
|
278
417
|
case 'MANUAL':
|
|
279
418
|
return 'Déclencheur manuel';
|
|
419
|
+
case 'TASK_COMPLETED':
|
|
420
|
+
return 'Tâche complétée';
|
|
421
|
+
case 'TRANSACTION_CREATED':
|
|
422
|
+
return 'Transaction créée';
|
|
423
|
+
case 'TRANSACTION_STATUS_CHANGED':
|
|
424
|
+
return 'Changement de statut de transaction';
|
|
425
|
+
case 'CONTACT_ASSIGNMENT_CHANGED':
|
|
426
|
+
return "Changement d'assignation";
|
|
280
427
|
default:
|
|
281
428
|
return '';
|
|
282
429
|
}
|
|
283
430
|
};
|
|
284
431
|
|
|
432
|
+
const selectClass =
|
|
433
|
+
'mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus:outline-none';
|
|
434
|
+
const selectClassSm =
|
|
435
|
+
'mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus:outline-none';
|
|
436
|
+
const inputClassSm = selectClassSm;
|
|
437
|
+
|
|
438
|
+
const renderActionConfig = (action: WorkflowAction, index: number) => {
|
|
439
|
+
switch (action.actionType) {
|
|
440
|
+
case 'SEND_EMAIL':
|
|
441
|
+
return (
|
|
442
|
+
<div className="mt-4">
|
|
443
|
+
<label className="block text-xs font-medium text-gray-700">Template email</label>
|
|
444
|
+
<select
|
|
445
|
+
value={action.emailTemplateId || ''}
|
|
446
|
+
onChange={(e) => updateAction(index, { emailTemplateId: e.target.value || null })}
|
|
447
|
+
className={selectClass}
|
|
448
|
+
>
|
|
449
|
+
<option value="">Sélectionner un template</option>
|
|
450
|
+
{emailTemplates.map((t) => (
|
|
451
|
+
<option key={t.id} value={t.id}>
|
|
452
|
+
{t.name}
|
|
453
|
+
</option>
|
|
454
|
+
))}
|
|
455
|
+
</select>
|
|
456
|
+
</div>
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
case 'SEND_SMS':
|
|
460
|
+
return (
|
|
461
|
+
<div className="mt-4">
|
|
462
|
+
<label className="block text-xs font-medium text-gray-700">Message SMS</label>
|
|
463
|
+
<textarea
|
|
464
|
+
value={action.smsMessage || ''}
|
|
465
|
+
onChange={(e) => updateAction(index, { smsMessage: e.target.value })}
|
|
466
|
+
rows={3}
|
|
467
|
+
className={selectClass}
|
|
468
|
+
placeholder="Votre message SMS... (variables: {firstName}, {lastName}, {phone})"
|
|
469
|
+
/>
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
case 'CHANGE_STATUS':
|
|
474
|
+
return (
|
|
475
|
+
<div className="mt-4">
|
|
476
|
+
<label className="block text-xs font-medium text-gray-700">Nouveau statut</label>
|
|
477
|
+
<select
|
|
478
|
+
value={action.newStatusId || ''}
|
|
479
|
+
onChange={(e) => updateAction(index, { newStatusId: e.target.value || null })}
|
|
480
|
+
className={selectClass}
|
|
481
|
+
>
|
|
482
|
+
<option value="">Sélectionner un statut</option>
|
|
483
|
+
{statuses.map((s) => (
|
|
484
|
+
<option key={s.id} value={s.id}>
|
|
485
|
+
{s.name}
|
|
486
|
+
</option>
|
|
487
|
+
))}
|
|
488
|
+
</select>
|
|
489
|
+
</div>
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
case 'CREATE_TASK':
|
|
493
|
+
return (
|
|
494
|
+
<div className="mt-4 space-y-3">
|
|
495
|
+
<div>
|
|
496
|
+
<label className="block text-xs font-medium text-gray-700">Titre de la tâche</label>
|
|
497
|
+
<input
|
|
498
|
+
type="text"
|
|
499
|
+
value={action.taskTitle || ''}
|
|
500
|
+
onChange={(e) => updateAction(index, { taskTitle: e.target.value })}
|
|
501
|
+
className={selectClass}
|
|
502
|
+
placeholder="Titre de la tâche..."
|
|
503
|
+
/>
|
|
504
|
+
</div>
|
|
505
|
+
<div>
|
|
506
|
+
<label className="block text-xs font-medium text-gray-700">Description</label>
|
|
507
|
+
<textarea
|
|
508
|
+
value={action.taskDescription || ''}
|
|
509
|
+
onChange={(e) => updateAction(index, { taskDescription: e.target.value })}
|
|
510
|
+
rows={2}
|
|
511
|
+
className={selectClass}
|
|
512
|
+
placeholder="Description de la tâche..."
|
|
513
|
+
/>
|
|
514
|
+
</div>
|
|
515
|
+
<div className="grid grid-cols-3 gap-2">
|
|
516
|
+
<div>
|
|
517
|
+
<label className="block text-xs font-medium text-gray-700">Type</label>
|
|
518
|
+
<select
|
|
519
|
+
value={action.taskType || 'OTHER'}
|
|
520
|
+
onChange={(e) => updateAction(index, { taskType: e.target.value })}
|
|
521
|
+
className={selectClass}
|
|
522
|
+
>
|
|
523
|
+
{TASK_TYPES.filter((t) => t.value).map((t) => (
|
|
524
|
+
<option key={t.value} value={t.value}>
|
|
525
|
+
{t.label}
|
|
526
|
+
</option>
|
|
527
|
+
))}
|
|
528
|
+
</select>
|
|
529
|
+
</div>
|
|
530
|
+
<div>
|
|
531
|
+
<label className="block text-xs font-medium text-gray-700">Priorité</label>
|
|
532
|
+
<select
|
|
533
|
+
value={action.taskPriority || 'MEDIUM'}
|
|
534
|
+
onChange={(e) => updateAction(index, { taskPriority: e.target.value })}
|
|
535
|
+
className={selectClass}
|
|
536
|
+
>
|
|
537
|
+
{TASK_PRIORITIES.map((p) => (
|
|
538
|
+
<option key={p.value} value={p.value}>
|
|
539
|
+
{p.label}
|
|
540
|
+
</option>
|
|
541
|
+
))}
|
|
542
|
+
</select>
|
|
543
|
+
</div>
|
|
544
|
+
<div>
|
|
545
|
+
<label className="block text-xs font-medium text-gray-700">Assigné à</label>
|
|
546
|
+
<select
|
|
547
|
+
value={action.taskAssignedUserId || ''}
|
|
548
|
+
onChange={(e) =>
|
|
549
|
+
updateAction(index, { taskAssignedUserId: e.target.value || null })
|
|
550
|
+
}
|
|
551
|
+
className={selectClass}
|
|
552
|
+
>
|
|
553
|
+
<option value="">Propriétaire du workflow</option>
|
|
554
|
+
{users.map((u) => (
|
|
555
|
+
<option key={u.id} value={u.id}>
|
|
556
|
+
{u.name}
|
|
557
|
+
</option>
|
|
558
|
+
))}
|
|
559
|
+
</select>
|
|
560
|
+
</div>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
case 'ASSIGN_CONTACT':
|
|
566
|
+
return (
|
|
567
|
+
<div className="mt-4 grid grid-cols-2 gap-3">
|
|
568
|
+
<div>
|
|
569
|
+
<label className="block text-xs font-medium text-gray-700">Commercial</label>
|
|
570
|
+
<select
|
|
571
|
+
value={action.assignCommercialId || ''}
|
|
572
|
+
onChange={(e) =>
|
|
573
|
+
updateAction(index, { assignCommercialId: e.target.value || null })
|
|
574
|
+
}
|
|
575
|
+
className={selectClass}
|
|
576
|
+
>
|
|
577
|
+
<option value="">Ne pas modifier</option>
|
|
578
|
+
{users.map((u) => (
|
|
579
|
+
<option key={u.id} value={u.id}>
|
|
580
|
+
{u.name}
|
|
581
|
+
</option>
|
|
582
|
+
))}
|
|
583
|
+
</select>
|
|
584
|
+
</div>
|
|
585
|
+
<div>
|
|
586
|
+
<label className="block text-xs font-medium text-gray-700">Télépro</label>
|
|
587
|
+
<select
|
|
588
|
+
value={action.assignTeleproId || ''}
|
|
589
|
+
onChange={(e) => updateAction(index, { assignTeleproId: e.target.value || null })}
|
|
590
|
+
className={selectClass}
|
|
591
|
+
>
|
|
592
|
+
<option value="">Ne pas modifier</option>
|
|
593
|
+
{users.map((u) => (
|
|
594
|
+
<option key={u.id} value={u.id}>
|
|
595
|
+
{u.name}
|
|
596
|
+
</option>
|
|
597
|
+
))}
|
|
598
|
+
</select>
|
|
599
|
+
</div>
|
|
600
|
+
</div>
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
case 'ADD_NOTE':
|
|
604
|
+
return (
|
|
605
|
+
<div className="mt-4">
|
|
606
|
+
<label className="block text-xs font-medium text-gray-700">Contenu de la note</label>
|
|
607
|
+
<textarea
|
|
608
|
+
value={action.noteContent || ''}
|
|
609
|
+
onChange={(e) => updateAction(index, { noteContent: e.target.value })}
|
|
610
|
+
rows={3}
|
|
611
|
+
className={selectClass}
|
|
612
|
+
placeholder="Contenu de la note... (variables: {{firstName}}, {{lastName}}, etc.)"
|
|
613
|
+
/>
|
|
614
|
+
</div>
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
case 'NOTIFY_USER':
|
|
618
|
+
return (
|
|
619
|
+
<div className="mt-4 space-y-3">
|
|
620
|
+
<div>
|
|
621
|
+
<label className="block text-xs font-medium text-gray-700">
|
|
622
|
+
Utilisateur à notifier
|
|
623
|
+
</label>
|
|
624
|
+
<select
|
|
625
|
+
value={action.notifyUserId || ''}
|
|
626
|
+
onChange={(e) => updateAction(index, { notifyUserId: e.target.value || null })}
|
|
627
|
+
className={selectClass}
|
|
628
|
+
>
|
|
629
|
+
<option value="">Sélectionner un utilisateur</option>
|
|
630
|
+
{users.map((u) => (
|
|
631
|
+
<option key={u.id} value={u.id}>
|
|
632
|
+
{u.name}
|
|
633
|
+
</option>
|
|
634
|
+
))}
|
|
635
|
+
</select>
|
|
636
|
+
</div>
|
|
637
|
+
<div>
|
|
638
|
+
<label className="block text-xs font-medium text-gray-700">Titre de la tâche</label>
|
|
639
|
+
<input
|
|
640
|
+
type="text"
|
|
641
|
+
value={action.taskTitle || ''}
|
|
642
|
+
onChange={(e) => updateAction(index, { taskTitle: e.target.value })}
|
|
643
|
+
className={selectClass}
|
|
644
|
+
placeholder="Ex : Relancer le contact {{firstName}} {{lastName}}"
|
|
645
|
+
/>
|
|
646
|
+
</div>
|
|
647
|
+
<div>
|
|
648
|
+
<label className="block text-xs font-medium text-gray-700">Description</label>
|
|
649
|
+
<textarea
|
|
650
|
+
value={action.taskDescription || ''}
|
|
651
|
+
onChange={(e) => updateAction(index, { taskDescription: e.target.value })}
|
|
652
|
+
rows={2}
|
|
653
|
+
className={selectClass}
|
|
654
|
+
placeholder="Description..."
|
|
655
|
+
/>
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
default:
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const renderConditions = (action: WorkflowAction, index: number) => (
|
|
666
|
+
<div className="mt-4 border-t border-dashed border-gray-200 pt-3">
|
|
667
|
+
<label className="block text-xs font-medium text-gray-700">Conditions (optionnel)</label>
|
|
668
|
+
|
|
669
|
+
{/* Condition sur le statut */}
|
|
670
|
+
<div className="mt-2 grid grid-cols-2 gap-2">
|
|
671
|
+
<select
|
|
672
|
+
value={action.conditionOperator || ''}
|
|
673
|
+
onChange={(e) =>
|
|
674
|
+
updateAction(index, {
|
|
675
|
+
conditionOperator: (e.target.value as 'EQUALS' | 'NOT_EQUALS') || null,
|
|
676
|
+
})
|
|
677
|
+
}
|
|
678
|
+
className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus:outline-none"
|
|
679
|
+
>
|
|
680
|
+
<option value="">Pas de condition statut</option>
|
|
681
|
+
<option value="EQUALS">Statut est égal à</option>
|
|
682
|
+
<option value="NOT_EQUALS">Statut n'est pas égal à</option>
|
|
683
|
+
</select>
|
|
684
|
+
{action.conditionOperator && (
|
|
685
|
+
<select
|
|
686
|
+
value={action.conditionStatusId || ''}
|
|
687
|
+
onChange={(e) => updateAction(index, { conditionStatusId: e.target.value || null })}
|
|
688
|
+
className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus:outline-none"
|
|
689
|
+
>
|
|
690
|
+
<option value="">Sélectionner...</option>
|
|
691
|
+
{statuses.map((s) => (
|
|
692
|
+
<option key={s.id} value={s.id}>
|
|
693
|
+
{s.name}
|
|
694
|
+
</option>
|
|
695
|
+
))}
|
|
696
|
+
</select>
|
|
697
|
+
)}
|
|
698
|
+
</div>
|
|
699
|
+
|
|
700
|
+
{/* Condition sur l'origine */}
|
|
701
|
+
<div className="mt-2">
|
|
702
|
+
<input
|
|
703
|
+
type="text"
|
|
704
|
+
value={action.conditionOrigin || ''}
|
|
705
|
+
onChange={(e) => updateAction(index, { conditionOrigin: e.target.value || null })}
|
|
706
|
+
className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus:outline-none"
|
|
707
|
+
placeholder="Origine du contact (laisser vide pour ignorer)"
|
|
708
|
+
/>
|
|
709
|
+
</div>
|
|
710
|
+
|
|
711
|
+
{/* Condition sur la société */}
|
|
712
|
+
<div className="mt-2">
|
|
713
|
+
<select
|
|
714
|
+
value={
|
|
715
|
+
action.conditionHasCompany === true
|
|
716
|
+
? 'true'
|
|
717
|
+
: action.conditionHasCompany === false
|
|
718
|
+
? 'false'
|
|
719
|
+
: ''
|
|
720
|
+
}
|
|
721
|
+
onChange={(e) => {
|
|
722
|
+
const val = e.target.value;
|
|
723
|
+
updateAction(index, {
|
|
724
|
+
conditionHasCompany: val === 'true' ? true : val === 'false' ? false : null,
|
|
725
|
+
});
|
|
726
|
+
}}
|
|
727
|
+
className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus:outline-none"
|
|
728
|
+
>
|
|
729
|
+
<option value="">Société : indifférent</option>
|
|
730
|
+
<option value="true">A une société</option>
|
|
731
|
+
<option value="false">N'a pas de société</option>
|
|
732
|
+
</select>
|
|
733
|
+
</div>
|
|
734
|
+
</div>
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
const actionMenuItems: Array<{ type: ActionType; label: string; icon: React.ReactNode }> = [
|
|
738
|
+
{ type: 'SEND_EMAIL', label: 'Envoyer un email', icon: <Mail className="h-4 w-4" /> },
|
|
739
|
+
{ type: 'SEND_SMS', label: 'Envoyer un SMS', icon: <MessageSquare className="h-4 w-4" /> },
|
|
740
|
+
{ type: 'CHANGE_STATUS', label: 'Changer le statut', icon: <Tag className="h-4 w-4" /> },
|
|
741
|
+
{ type: 'CREATE_TASK', label: 'Créer une tâche', icon: <CheckSquare className="h-4 w-4" /> },
|
|
742
|
+
{
|
|
743
|
+
type: 'ASSIGN_CONTACT',
|
|
744
|
+
label: 'Assigner le contact',
|
|
745
|
+
icon: <UserPlus className="h-4 w-4" />,
|
|
746
|
+
},
|
|
747
|
+
{ type: 'ADD_NOTE', label: 'Ajouter une note', icon: <StickyNote className="h-4 w-4" /> },
|
|
748
|
+
{ type: 'NOTIFY_USER', label: 'Notifier un utilisateur', icon: <Bell className="h-4 w-4" /> },
|
|
749
|
+
{ type: 'WAIT', label: 'Attendre', icon: <Clock className="h-4 w-4" /> },
|
|
750
|
+
];
|
|
751
|
+
|
|
285
752
|
return (
|
|
286
753
|
<form onSubmit={handleSubmit} className="h-full">
|
|
287
754
|
<div className="grid gap-6 lg:grid-cols-[minmax(0,2fr)_minmax(320px,1fr)]">
|
|
288
755
|
{/* Colonne centrale : flux d'actions */}
|
|
289
756
|
<div className="space-y-6">
|
|
290
|
-
<div className="rounded-2xl border border-dashed border-
|
|
757
|
+
<div className="rounded-2xl border border-dashed border-blue-200 bg-blue-50/40 p-4 text-sm text-blue-900">
|
|
291
758
|
<p className="font-medium">Flux du workflow</p>
|
|
292
|
-
<p className="mt-1 text-xs text-
|
|
293
|
-
Visualisez l
|
|
759
|
+
<p className="mt-1 text-xs text-blue-700">
|
|
760
|
+
Visualisez l'enchaînement du déclencheur et des actions. Ajoutez des étapes pour
|
|
294
761
|
construire votre automatisation.
|
|
295
762
|
</p>
|
|
296
763
|
</div>
|
|
297
764
|
|
|
298
765
|
<div className="relative space-y-4">
|
|
299
|
-
|
|
300
|
-
<div className="pointer-events-none absolute top-0 left-6 h-full w-px bg-linear-to-b from-indigo-300 via-gray-200 to-gray-200" />
|
|
766
|
+
<div className="pointer-events-none absolute top-0 left-6 h-full w-px bg-linear-to-b from-blue-300 via-gray-200 to-gray-200" />
|
|
301
767
|
|
|
302
768
|
{/* Déclencheur */}
|
|
303
769
|
<div className="relative pl-10">
|
|
304
|
-
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-white bg-
|
|
305
|
-
<div className="rounded-xl border border-
|
|
770
|
+
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-white bg-blue-500 shadow-sm" />
|
|
771
|
+
<div className="rounded-xl border border-blue-100 bg-white px-4 py-3 shadow-sm">
|
|
306
772
|
<div className="flex items-center justify-between">
|
|
307
773
|
<div>
|
|
308
|
-
<p className="text-xs font-semibold tracking-wide text-
|
|
774
|
+
<p className="text-xs font-semibold tracking-wide text-blue-600 uppercase">
|
|
309
775
|
Déclencheur
|
|
310
776
|
</p>
|
|
311
777
|
<p className="mt-1 text-sm font-medium text-gray-900">
|
|
@@ -331,11 +797,11 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
331
797
|
) : (
|
|
332
798
|
actions.map((action, index) => (
|
|
333
799
|
<div key={index} className="relative pl-10">
|
|
334
|
-
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-white bg-
|
|
800
|
+
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-white bg-blue-500 shadow-sm" />
|
|
335
801
|
<div className="rounded-xl border border-gray-200 bg-white p-4 shadow-sm transition-shadow hover:shadow-md">
|
|
336
802
|
<div className="mb-3 flex items-center justify-between">
|
|
337
803
|
<div className="flex items-center gap-3">
|
|
338
|
-
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-
|
|
804
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-50 text-xs font-semibold text-blue-700">
|
|
339
805
|
{index + 1}
|
|
340
806
|
</div>
|
|
341
807
|
<div>
|
|
@@ -347,7 +813,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
347
813
|
</div>
|
|
348
814
|
<p className="mt-0.5 text-xs text-gray-500">
|
|
349
815
|
Délai de {action.delayDays || 0}j {action.delayHours || 0}h après
|
|
350
|
-
l
|
|
816
|
+
l'étape précédente
|
|
351
817
|
</p>
|
|
352
818
|
</div>
|
|
353
819
|
</div>
|
|
@@ -363,22 +829,20 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
363
829
|
<div className="grid gap-4 md:grid-cols-2">
|
|
364
830
|
<div>
|
|
365
831
|
<label className="block text-xs font-medium text-gray-700">
|
|
366
|
-
Type d
|
|
832
|
+
Type d'action
|
|
367
833
|
</label>
|
|
368
834
|
<select
|
|
369
835
|
value={action.actionType}
|
|
370
836
|
onChange={(e) =>
|
|
371
|
-
updateAction(index, {
|
|
372
|
-
actionType: e.target.value as WorkflowAction['actionType'],
|
|
373
|
-
})
|
|
837
|
+
updateAction(index, { actionType: e.target.value as ActionType })
|
|
374
838
|
}
|
|
375
|
-
className=
|
|
839
|
+
className={selectClass}
|
|
376
840
|
>
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
841
|
+
{actionMenuItems.map((item) => (
|
|
842
|
+
<option key={item.type} value={item.type}>
|
|
843
|
+
{item.label}
|
|
844
|
+
</option>
|
|
845
|
+
))}
|
|
382
846
|
</select>
|
|
383
847
|
</div>
|
|
384
848
|
<div className="grid grid-cols-2 gap-2">
|
|
@@ -387,179 +851,38 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
387
851
|
Délai (jours)
|
|
388
852
|
</label>
|
|
389
853
|
<input
|
|
390
|
-
type="
|
|
391
|
-
|
|
854
|
+
type="text"
|
|
855
|
+
inputMode="numeric"
|
|
392
856
|
value={action.delayDays}
|
|
393
|
-
onChange={(e) =>
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
857
|
+
onChange={(e) => {
|
|
858
|
+
const value = e.target.value;
|
|
859
|
+
if (value === '' || /^\d+$/.test(value)) {
|
|
860
|
+
updateAction(index, { delayDays: parseInt(value, 10) || 0 });
|
|
861
|
+
}
|
|
862
|
+
}}
|
|
863
|
+
className={selectClass}
|
|
399
864
|
/>
|
|
400
865
|
</div>
|
|
401
866
|
<div>
|
|
402
867
|
<label className="block text-xs font-medium text-gray-700">Heures</label>
|
|
403
868
|
<input
|
|
404
|
-
type="
|
|
405
|
-
|
|
869
|
+
type="text"
|
|
870
|
+
inputMode="numeric"
|
|
406
871
|
value={action.delayHours}
|
|
407
|
-
onChange={(e) =>
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
872
|
+
onChange={(e) => {
|
|
873
|
+
const value = e.target.value;
|
|
874
|
+
if (value === '' || /^\d+$/.test(value)) {
|
|
875
|
+
updateAction(index, { delayHours: parseInt(value, 10) || 0 });
|
|
876
|
+
}
|
|
877
|
+
}}
|
|
878
|
+
className={selectClass}
|
|
413
879
|
/>
|
|
414
880
|
</div>
|
|
415
881
|
</div>
|
|
416
882
|
</div>
|
|
417
883
|
|
|
418
|
-
{
|
|
419
|
-
{action
|
|
420
|
-
<div className="mt-4">
|
|
421
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
422
|
-
Template email
|
|
423
|
-
</label>
|
|
424
|
-
<select
|
|
425
|
-
value={action.emailTemplateId || ''}
|
|
426
|
-
onChange={(e) =>
|
|
427
|
-
updateAction(index, {
|
|
428
|
-
emailTemplateId: e.target.value || null,
|
|
429
|
-
})
|
|
430
|
-
}
|
|
431
|
-
className="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
432
|
-
>
|
|
433
|
-
<option value="">Sélectionner un template</option>
|
|
434
|
-
{emailTemplates.map((template) => (
|
|
435
|
-
<option key={template.id} value={template.id}>
|
|
436
|
-
{template.name}
|
|
437
|
-
</option>
|
|
438
|
-
))}
|
|
439
|
-
</select>
|
|
440
|
-
</div>
|
|
441
|
-
)}
|
|
442
|
-
|
|
443
|
-
{action.actionType === 'SEND_SMS' && (
|
|
444
|
-
<div className="mt-4">
|
|
445
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
446
|
-
Message SMS
|
|
447
|
-
</label>
|
|
448
|
-
<textarea
|
|
449
|
-
value={action.smsMessage || ''}
|
|
450
|
-
onChange={(e) =>
|
|
451
|
-
updateAction(index, {
|
|
452
|
-
smsMessage: e.target.value,
|
|
453
|
-
})
|
|
454
|
-
}
|
|
455
|
-
rows={3}
|
|
456
|
-
className="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
457
|
-
placeholder="Votre message SMS..."
|
|
458
|
-
/>
|
|
459
|
-
</div>
|
|
460
|
-
)}
|
|
461
|
-
|
|
462
|
-
{action.actionType === 'CHANGE_STATUS' && (
|
|
463
|
-
<div className="mt-4">
|
|
464
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
465
|
-
Nouveau statut
|
|
466
|
-
</label>
|
|
467
|
-
<select
|
|
468
|
-
value={action.newStatusId || ''}
|
|
469
|
-
onChange={(e) =>
|
|
470
|
-
updateAction(index, {
|
|
471
|
-
newStatusId: e.target.value || null,
|
|
472
|
-
})
|
|
473
|
-
}
|
|
474
|
-
className="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
475
|
-
>
|
|
476
|
-
<option value="">Sélectionner un statut</option>
|
|
477
|
-
{statuses.map((status) => (
|
|
478
|
-
<option key={status.id} value={status.id}>
|
|
479
|
-
{status.name}
|
|
480
|
-
</option>
|
|
481
|
-
))}
|
|
482
|
-
</select>
|
|
483
|
-
</div>
|
|
484
|
-
)}
|
|
485
|
-
|
|
486
|
-
{action.actionType === 'CREATE_TASK' && (
|
|
487
|
-
<div className="mt-4 space-y-3">
|
|
488
|
-
<div>
|
|
489
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
490
|
-
Titre de la tâche
|
|
491
|
-
</label>
|
|
492
|
-
<input
|
|
493
|
-
type="text"
|
|
494
|
-
value={action.taskTitle || ''}
|
|
495
|
-
onChange={(e) =>
|
|
496
|
-
updateAction(index, {
|
|
497
|
-
taskTitle: e.target.value,
|
|
498
|
-
})
|
|
499
|
-
}
|
|
500
|
-
className="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
501
|
-
placeholder="Titre de la tâche..."
|
|
502
|
-
/>
|
|
503
|
-
</div>
|
|
504
|
-
<div>
|
|
505
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
506
|
-
Description
|
|
507
|
-
</label>
|
|
508
|
-
<textarea
|
|
509
|
-
value={action.taskDescription || ''}
|
|
510
|
-
onChange={(e) =>
|
|
511
|
-
updateAction(index, {
|
|
512
|
-
taskDescription: e.target.value,
|
|
513
|
-
})
|
|
514
|
-
}
|
|
515
|
-
rows={3}
|
|
516
|
-
className="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
517
|
-
placeholder="Description de la tâche..."
|
|
518
|
-
/>
|
|
519
|
-
</div>
|
|
520
|
-
</div>
|
|
521
|
-
)}
|
|
522
|
-
|
|
523
|
-
{/* Condition */}
|
|
524
|
-
<div className="mt-4 border-t border-dashed border-gray-200 pt-3">
|
|
525
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
526
|
-
Condition (optionnel)
|
|
527
|
-
</label>
|
|
528
|
-
<div className="mt-2 grid grid-cols-2 gap-2">
|
|
529
|
-
<select
|
|
530
|
-
value={action.conditionOperator || ''}
|
|
531
|
-
onChange={(e) =>
|
|
532
|
-
updateAction(index, {
|
|
533
|
-
conditionOperator:
|
|
534
|
-
(e.target.value as 'EQUALS' | 'NOT_EQUALS') || null,
|
|
535
|
-
})
|
|
536
|
-
}
|
|
537
|
-
className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
538
|
-
>
|
|
539
|
-
<option value="">Aucune condition</option>
|
|
540
|
-
<option value="EQUALS">Statut est égal à</option>
|
|
541
|
-
<option value="NOT_EQUALS">Statut n'est pas égal à</option>
|
|
542
|
-
</select>
|
|
543
|
-
{action.conditionOperator && (
|
|
544
|
-
<select
|
|
545
|
-
value={action.conditionStatusId || ''}
|
|
546
|
-
onChange={(e) =>
|
|
547
|
-
updateAction(index, {
|
|
548
|
-
conditionStatusId: e.target.value || null,
|
|
549
|
-
})
|
|
550
|
-
}
|
|
551
|
-
className="block w-full rounded-lg border border-gray-300 px-3 py-2 text-xs text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
552
|
-
>
|
|
553
|
-
<option value="">Sélectionner...</option>
|
|
554
|
-
{statuses.map((status) => (
|
|
555
|
-
<option key={status.id} value={status.id}>
|
|
556
|
-
{status.name}
|
|
557
|
-
</option>
|
|
558
|
-
))}
|
|
559
|
-
</select>
|
|
560
|
-
)}
|
|
561
|
-
</div>
|
|
562
|
-
</div>
|
|
884
|
+
{renderActionConfig(action, index)}
|
|
885
|
+
{renderConditions(action, index)}
|
|
563
886
|
</div>
|
|
564
887
|
</div>
|
|
565
888
|
))
|
|
@@ -567,13 +890,13 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
567
890
|
|
|
568
891
|
{/* Bouton ajouter une action */}
|
|
569
892
|
<div className="relative pl-10">
|
|
570
|
-
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-dashed border-
|
|
893
|
+
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-dashed border-blue-300 bg-white" />
|
|
571
894
|
<div className="flex items-center justify-center">
|
|
572
895
|
<div className="relative inline-flex items-center" ref={actionMenuRef}>
|
|
573
896
|
<button
|
|
574
897
|
type="button"
|
|
575
898
|
onClick={() => setShowActionMenu(!showActionMenu)}
|
|
576
|
-
className="inline-flex cursor-pointer items-center gap-2 rounded-full border border-
|
|
899
|
+
className="inline-flex cursor-pointer items-center gap-2 rounded-full border border-blue-200 bg-white px-4 py-2 text-xs font-medium text-blue-700 shadow-sm transition-colors hover:border-blue-300 hover:bg-blue-50"
|
|
577
900
|
>
|
|
578
901
|
<Plus className="h-4 w-4" />
|
|
579
902
|
Ajouter une action
|
|
@@ -581,63 +904,19 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
581
904
|
{showActionMenu && (
|
|
582
905
|
<div className="absolute top-full left-1/2 z-10 mt-2 w-60 -translate-x-1/2 rounded-xl border border-gray-200 bg-white p-2 text-xs shadow-lg">
|
|
583
906
|
<p className="px-2 pb-1 text-[11px] font-medium tracking-wide text-gray-500 uppercase">
|
|
584
|
-
Types d
|
|
907
|
+
Types d'actions
|
|
585
908
|
</p>
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
<button
|
|
598
|
-
type="button"
|
|
599
|
-
onClick={() => {
|
|
600
|
-
addAction('SEND_SMS');
|
|
601
|
-
setShowActionMenu(false);
|
|
602
|
-
}}
|
|
603
|
-
className="flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-gray-700 transition-colors hover:bg-gray-50"
|
|
604
|
-
>
|
|
605
|
-
<MessageSquare className="h-4 w-4" />
|
|
606
|
-
Envoyer un SMS
|
|
607
|
-
</button>
|
|
608
|
-
<button
|
|
609
|
-
type="button"
|
|
610
|
-
onClick={() => {
|
|
611
|
-
addAction('CHANGE_STATUS');
|
|
612
|
-
setShowActionMenu(false);
|
|
613
|
-
}}
|
|
614
|
-
className="flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-gray-700 transition-colors hover:bg-gray-50"
|
|
615
|
-
>
|
|
616
|
-
<Tag className="h-4 w-4" />
|
|
617
|
-
Changer le statut
|
|
618
|
-
</button>
|
|
619
|
-
<button
|
|
620
|
-
type="button"
|
|
621
|
-
onClick={() => {
|
|
622
|
-
addAction('CREATE_TASK');
|
|
623
|
-
setShowActionMenu(false);
|
|
624
|
-
}}
|
|
625
|
-
className="flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-gray-700 transition-colors hover:bg-gray-50"
|
|
626
|
-
>
|
|
627
|
-
<CheckSquare className="h-4 w-4" />
|
|
628
|
-
Créer une tâche
|
|
629
|
-
</button>
|
|
630
|
-
<button
|
|
631
|
-
type="button"
|
|
632
|
-
onClick={() => {
|
|
633
|
-
addAction('WAIT');
|
|
634
|
-
setShowActionMenu(false);
|
|
635
|
-
}}
|
|
636
|
-
className="flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-gray-700 transition-colors hover:bg-gray-50"
|
|
637
|
-
>
|
|
638
|
-
<Clock className="h-4 w-4" />
|
|
639
|
-
Attendre
|
|
640
|
-
</button>
|
|
909
|
+
{actionMenuItems.map((item) => (
|
|
910
|
+
<button
|
|
911
|
+
key={item.type}
|
|
912
|
+
type="button"
|
|
913
|
+
onClick={() => addAction(item.type)}
|
|
914
|
+
className="flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-gray-700 transition-colors hover:bg-gray-50"
|
|
915
|
+
>
|
|
916
|
+
{item.icon}
|
|
917
|
+
{item.label}
|
|
918
|
+
</button>
|
|
919
|
+
))}
|
|
641
920
|
</div>
|
|
642
921
|
)}
|
|
643
922
|
</div>
|
|
@@ -675,13 +954,8 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
675
954
|
type="text"
|
|
676
955
|
required
|
|
677
956
|
value={formData.name}
|
|
678
|
-
onChange={(e) =>
|
|
679
|
-
|
|
680
|
-
...prev,
|
|
681
|
-
name: e.target.value,
|
|
682
|
-
}))
|
|
683
|
-
}
|
|
684
|
-
className="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
957
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
|
|
958
|
+
className={inputClassSm}
|
|
685
959
|
placeholder="Ex : Séquence de bienvenue nouveau lead"
|
|
686
960
|
/>
|
|
687
961
|
</div>
|
|
@@ -691,13 +965,10 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
691
965
|
<textarea
|
|
692
966
|
value={formData.description ?? ''}
|
|
693
967
|
onChange={(e) =>
|
|
694
|
-
setFormData((prev) => ({
|
|
695
|
-
...prev,
|
|
696
|
-
description: e.target.value,
|
|
697
|
-
}))
|
|
968
|
+
setFormData((prev) => ({ ...prev, description: e.target.value }))
|
|
698
969
|
}
|
|
699
970
|
rows={3}
|
|
700
|
-
className=
|
|
971
|
+
className={inputClassSm}
|
|
701
972
|
placeholder="Expliquez brièvement ce que fait ce workflow..."
|
|
702
973
|
/>
|
|
703
974
|
</div>
|
|
@@ -711,16 +982,11 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
711
982
|
</div>
|
|
712
983
|
<button
|
|
713
984
|
type="button"
|
|
714
|
-
onClick={() =>
|
|
715
|
-
setFormData((prev) => ({
|
|
716
|
-
...prev,
|
|
717
|
-
active: !prev.active,
|
|
718
|
-
}))
|
|
719
|
-
}
|
|
985
|
+
onClick={() => setFormData((prev) => ({ ...prev, active: !prev.active }))}
|
|
720
986
|
className={cn(
|
|
721
987
|
'relative inline-flex h-6 w-11 cursor-pointer items-center rounded-full border transition-colors',
|
|
722
988
|
formData.active
|
|
723
|
-
? 'border-
|
|
989
|
+
? 'border-blue-500 bg-blue-500'
|
|
724
990
|
: 'border-gray-300 bg-gray-200',
|
|
725
991
|
)}
|
|
726
992
|
>
|
|
@@ -745,18 +1011,25 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
745
1011
|
onChange={(e) =>
|
|
746
1012
|
setFormData((prev) => ({
|
|
747
1013
|
...prev,
|
|
748
|
-
triggerType: e.target.value as
|
|
1014
|
+
triggerType: e.target.value as TriggerType,
|
|
749
1015
|
}))
|
|
750
1016
|
}
|
|
751
|
-
className=
|
|
1017
|
+
className={selectClassSm}
|
|
752
1018
|
>
|
|
753
1019
|
<option value="CONTACT_CREATED">Nouveau contact créé</option>
|
|
754
1020
|
<option value="STATUS_CHANGED">Changement de statut</option>
|
|
755
1021
|
<option value="TIME_BASED">Basé sur le temps</option>
|
|
1022
|
+
<option value="TASK_COMPLETED">Tâche complétée</option>
|
|
1023
|
+
<option value="TRANSACTION_CREATED">Transaction créée</option>
|
|
1024
|
+
<option value="TRANSACTION_STATUS_CHANGED">
|
|
1025
|
+
Changement de statut de transaction
|
|
1026
|
+
</option>
|
|
1027
|
+
<option value="CONTACT_ASSIGNMENT_CHANGED">Changement d'assignation</option>
|
|
756
1028
|
<option value="MANUAL">Déclencheur manuel</option>
|
|
757
1029
|
</select>
|
|
758
1030
|
</div>
|
|
759
1031
|
|
|
1032
|
+
{/* STATUS_CHANGED config */}
|
|
760
1033
|
{formData.triggerType === 'STATUS_CHANGED' && (
|
|
761
1034
|
<div className="grid gap-3 md:grid-cols-2">
|
|
762
1035
|
<div>
|
|
@@ -771,12 +1044,12 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
771
1044
|
triggerFromStatusId: e.target.value || null,
|
|
772
1045
|
}))
|
|
773
1046
|
}
|
|
774
|
-
className=
|
|
1047
|
+
className={selectClassSm}
|
|
775
1048
|
>
|
|
776
1049
|
<option value="">Tous les statuts</option>
|
|
777
|
-
{statuses.map((
|
|
778
|
-
<option key={
|
|
779
|
-
{
|
|
1050
|
+
{statuses.map((s) => (
|
|
1051
|
+
<option key={s.id} value={s.id}>
|
|
1052
|
+
{s.name}
|
|
780
1053
|
</option>
|
|
781
1054
|
))}
|
|
782
1055
|
</select>
|
|
@@ -791,12 +1064,12 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
791
1064
|
triggerToStatusId: e.target.value || null,
|
|
792
1065
|
}))
|
|
793
1066
|
}
|
|
794
|
-
className=
|
|
1067
|
+
className={selectClassSm}
|
|
795
1068
|
>
|
|
796
1069
|
<option value="">Tous les statuts</option>
|
|
797
|
-
{statuses.map((
|
|
798
|
-
<option key={
|
|
799
|
-
{
|
|
1070
|
+
{statuses.map((s) => (
|
|
1071
|
+
<option key={s.id} value={s.id}>
|
|
1072
|
+
{s.name}
|
|
800
1073
|
</option>
|
|
801
1074
|
))}
|
|
802
1075
|
</select>
|
|
@@ -804,57 +1077,141 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
804
1077
|
</div>
|
|
805
1078
|
)}
|
|
806
1079
|
|
|
1080
|
+
{/* TIME_BASED config */}
|
|
807
1081
|
{formData.triggerType === 'TIME_BASED' && (
|
|
1082
|
+
<div className="space-y-3">
|
|
1083
|
+
<div>
|
|
1084
|
+
<label className="block text-xs font-medium text-gray-700">
|
|
1085
|
+
Événement de référence
|
|
1086
|
+
</label>
|
|
1087
|
+
<select
|
|
1088
|
+
value={formData.triggerTimeReference ?? 'CONTACT_CREATED_DATE'}
|
|
1089
|
+
onChange={(e) =>
|
|
1090
|
+
setFormData((prev) => ({ ...prev, triggerTimeReference: e.target.value }))
|
|
1091
|
+
}
|
|
1092
|
+
className={selectClassSm}
|
|
1093
|
+
>
|
|
1094
|
+
{TIME_REFERENCES.map((r) => (
|
|
1095
|
+
<option key={r.value} value={r.value}>
|
|
1096
|
+
{r.label}
|
|
1097
|
+
</option>
|
|
1098
|
+
))}
|
|
1099
|
+
</select>
|
|
1100
|
+
</div>
|
|
1101
|
+
<div className="grid gap-3 md:grid-cols-2">
|
|
1102
|
+
<div>
|
|
1103
|
+
<label className="block text-xs font-medium text-gray-700">Délai (jours)</label>
|
|
1104
|
+
<input
|
|
1105
|
+
type="text"
|
|
1106
|
+
inputMode="numeric"
|
|
1107
|
+
value={formData.triggerTimeDays ?? 0}
|
|
1108
|
+
onChange={(e) => {
|
|
1109
|
+
const value = e.target.value;
|
|
1110
|
+
if (value === '' || /^\d+$/.test(value)) {
|
|
1111
|
+
setFormData((prev) => ({
|
|
1112
|
+
...prev,
|
|
1113
|
+
triggerTimeDays: parseInt(value, 10) || 0,
|
|
1114
|
+
}));
|
|
1115
|
+
}
|
|
1116
|
+
}}
|
|
1117
|
+
className={inputClassSm}
|
|
1118
|
+
/>
|
|
1119
|
+
</div>
|
|
1120
|
+
<div>
|
|
1121
|
+
<label className="block text-xs font-medium text-gray-700">
|
|
1122
|
+
Délai (heures)
|
|
1123
|
+
</label>
|
|
1124
|
+
<input
|
|
1125
|
+
type="text"
|
|
1126
|
+
inputMode="numeric"
|
|
1127
|
+
value={formData.triggerTimeHours ?? 0}
|
|
1128
|
+
onChange={(e) => {
|
|
1129
|
+
const value = e.target.value;
|
|
1130
|
+
if (value === '' || /^\d+$/.test(value)) {
|
|
1131
|
+
setFormData((prev) => ({
|
|
1132
|
+
...prev,
|
|
1133
|
+
triggerTimeHours: parseInt(value, 10) || 0,
|
|
1134
|
+
}));
|
|
1135
|
+
}
|
|
1136
|
+
}}
|
|
1137
|
+
className={inputClassSm}
|
|
1138
|
+
/>
|
|
1139
|
+
</div>
|
|
1140
|
+
</div>
|
|
1141
|
+
</div>
|
|
1142
|
+
)}
|
|
1143
|
+
|
|
1144
|
+
{/* TASK_COMPLETED config */}
|
|
1145
|
+
{formData.triggerType === 'TASK_COMPLETED' && (
|
|
1146
|
+
<div>
|
|
1147
|
+
<label className="block text-xs font-medium text-gray-700">
|
|
1148
|
+
Type de tâche (optionnel)
|
|
1149
|
+
</label>
|
|
1150
|
+
<select
|
|
1151
|
+
value={formData.triggerTaskType ?? ''}
|
|
1152
|
+
onChange={(e) =>
|
|
1153
|
+
setFormData((prev) => ({ ...prev, triggerTaskType: e.target.value || null }))
|
|
1154
|
+
}
|
|
1155
|
+
className={selectClassSm}
|
|
1156
|
+
>
|
|
1157
|
+
{TASK_TYPES.map((t) => (
|
|
1158
|
+
<option key={t.value} value={t.value}>
|
|
1159
|
+
{t.label}
|
|
1160
|
+
</option>
|
|
1161
|
+
))}
|
|
1162
|
+
</select>
|
|
1163
|
+
</div>
|
|
1164
|
+
)}
|
|
1165
|
+
|
|
1166
|
+
{/* TRANSACTION_STATUS_CHANGED config */}
|
|
1167
|
+
{formData.triggerType === 'TRANSACTION_STATUS_CHANGED' && (
|
|
808
1168
|
<div className="grid gap-3 md:grid-cols-2">
|
|
809
1169
|
<div>
|
|
810
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
value={formData.
|
|
1170
|
+
<label className="block text-xs font-medium text-gray-700">
|
|
1171
|
+
Du statut (optionnel)
|
|
1172
|
+
</label>
|
|
1173
|
+
<select
|
|
1174
|
+
value={formData.triggerTransactionFromStatus ?? ''}
|
|
815
1175
|
onChange={(e) =>
|
|
816
1176
|
setFormData((prev) => ({
|
|
817
1177
|
...prev,
|
|
818
|
-
|
|
1178
|
+
triggerTransactionFromStatus: e.target.value || null,
|
|
819
1179
|
}))
|
|
820
1180
|
}
|
|
821
|
-
className=
|
|
822
|
-
|
|
1181
|
+
className={selectClassSm}
|
|
1182
|
+
>
|
|
1183
|
+
<option value="">Tous les statuts</option>
|
|
1184
|
+
{TRANSACTION_STATUSES.map((s) => (
|
|
1185
|
+
<option key={s.value} value={s.value}>
|
|
1186
|
+
{s.label}
|
|
1187
|
+
</option>
|
|
1188
|
+
))}
|
|
1189
|
+
</select>
|
|
823
1190
|
</div>
|
|
824
1191
|
<div>
|
|
825
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
826
|
-
<
|
|
827
|
-
|
|
828
|
-
min="0"
|
|
829
|
-
value={formData.triggerTimeHours ?? 0}
|
|
1192
|
+
<label className="block text-xs font-medium text-gray-700">Vers le statut</label>
|
|
1193
|
+
<select
|
|
1194
|
+
value={formData.triggerTransactionToStatus ?? ''}
|
|
830
1195
|
onChange={(e) =>
|
|
831
1196
|
setFormData((prev) => ({
|
|
832
1197
|
...prev,
|
|
833
|
-
|
|
1198
|
+
triggerTransactionToStatus: e.target.value || null,
|
|
834
1199
|
}))
|
|
835
1200
|
}
|
|
836
|
-
className=
|
|
837
|
-
|
|
1201
|
+
className={selectClassSm}
|
|
1202
|
+
>
|
|
1203
|
+
<option value="">Tous les statuts</option>
|
|
1204
|
+
{TRANSACTION_STATUSES.map((s) => (
|
|
1205
|
+
<option key={s.value} value={s.value}>
|
|
1206
|
+
{s.label}
|
|
1207
|
+
</option>
|
|
1208
|
+
))}
|
|
1209
|
+
</select>
|
|
838
1210
|
</div>
|
|
839
1211
|
</div>
|
|
840
1212
|
)}
|
|
841
1213
|
</div>
|
|
842
1214
|
|
|
843
|
-
{(error || success) && (
|
|
844
|
-
<div className="space-y-2 text-xs">
|
|
845
|
-
{error && (
|
|
846
|
-
<div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-red-700">
|
|
847
|
-
{error}
|
|
848
|
-
</div>
|
|
849
|
-
)}
|
|
850
|
-
{success && (
|
|
851
|
-
<div className="rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-green-700">
|
|
852
|
-
{success}
|
|
853
|
-
</div>
|
|
854
|
-
)}
|
|
855
|
-
</div>
|
|
856
|
-
)}
|
|
857
|
-
|
|
858
1215
|
<div className="flex flex-col gap-2 pt-2 sm:flex-row sm:justify-end">
|
|
859
1216
|
<button
|
|
860
1217
|
type="button"
|
|
@@ -866,7 +1223,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
866
1223
|
<button
|
|
867
1224
|
type="submit"
|
|
868
1225
|
disabled={saving || loading}
|
|
869
|
-
className="w-full cursor-pointer rounded-xl bg-
|
|
1226
|
+
className="w-full cursor-pointer rounded-xl bg-blue-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-gray-400/30 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:bg-blue-400 sm:w-auto"
|
|
870
1227
|
>
|
|
871
1228
|
{saving ? 'Enregistrement...' : 'Enregistrer le workflow'}
|
|
872
1229
|
</button>
|