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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { auth } from '@/lib/auth';
|
|
3
3
|
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
5
|
+
import { executeWorkflowsOnTaskCompleted } from '@/lib/workflow-executor';
|
|
4
6
|
import {
|
|
5
7
|
getValidAccessToken,
|
|
6
8
|
updateGoogleCalendarEvent,
|
|
@@ -9,7 +11,7 @@ import {
|
|
|
9
11
|
getGoogleCalendarEvent,
|
|
10
12
|
} from '@/lib/google-calendar';
|
|
11
13
|
import nodemailer from 'nodemailer';
|
|
12
|
-
import { decrypt } from '@/lib/encryption';
|
|
14
|
+
import { decrypt, encrypt } from '@/lib/encryption';
|
|
13
15
|
import { logAppointmentCancelled, logAppointmentChanged } from '@/lib/contact-interactions';
|
|
14
16
|
import { render } from '@react-email/render';
|
|
15
17
|
import { MeetUpdateEmailTemplate } from '@/components/meet-update-email-template';
|
|
@@ -37,6 +39,15 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
37
39
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
const [canViewAll, canViewOwn] = await Promise.all([
|
|
43
|
+
checkPermission('tasks.view_all'),
|
|
44
|
+
checkPermission('tasks.view_own'),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
if (!canViewAll && !canViewOwn) {
|
|
48
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
const { id } = await params;
|
|
41
52
|
|
|
42
53
|
const task = await prisma.task.findUnique({
|
|
@@ -72,13 +83,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
72
83
|
return NextResponse.json({ error: 'Tâche non trouvée' }, { status: 404 });
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
const user = await prisma.user.findUnique({
|
|
77
|
-
where: { id: session.user.id },
|
|
78
|
-
select: { role: true },
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (task.assignedUserId !== session.user.id && user?.role !== 'ADMIN') {
|
|
86
|
+
if (task.assignedUserId !== session.user.id && !canViewAll) {
|
|
82
87
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -100,6 +105,15 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
100
105
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
101
106
|
}
|
|
102
107
|
|
|
108
|
+
const [canEditAll, canEditOwn] = await Promise.all([
|
|
109
|
+
checkPermission('tasks.edit_all'),
|
|
110
|
+
checkPermission('tasks.edit_own'),
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
if (!canEditAll && !canEditOwn) {
|
|
114
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
115
|
+
}
|
|
116
|
+
|
|
103
117
|
const { id } = await params;
|
|
104
118
|
const body = await request.json();
|
|
105
119
|
const {
|
|
@@ -115,6 +129,12 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
115
129
|
attendees,
|
|
116
130
|
notifyContact,
|
|
117
131
|
internalNote,
|
|
132
|
+
// Champs d'adresse pour les rendez-vous physiques
|
|
133
|
+
location,
|
|
134
|
+
locationAddress,
|
|
135
|
+
locationCity,
|
|
136
|
+
locationPostalCode,
|
|
137
|
+
isAtHome,
|
|
118
138
|
} = body;
|
|
119
139
|
|
|
120
140
|
// Vérifier que la tâche existe
|
|
@@ -126,13 +146,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
126
146
|
return NextResponse.json({ error: 'Tâche non trouvée' }, { status: 404 });
|
|
127
147
|
}
|
|
128
148
|
|
|
129
|
-
|
|
130
|
-
const user = await prisma.user.findUnique({
|
|
131
|
-
where: { id: session.user.id },
|
|
132
|
-
select: { role: true },
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
if (existingTask.assignedUserId !== session.user.id && user?.role !== 'ADMIN') {
|
|
149
|
+
if (!canEditAll && existingTask.assignedUserId !== session.user.id) {
|
|
136
150
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
137
151
|
}
|
|
138
152
|
|
|
@@ -161,95 +175,101 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
161
175
|
updateData.completedAt = completed ? new Date() : null;
|
|
162
176
|
}
|
|
163
177
|
|
|
164
|
-
//
|
|
165
|
-
if (
|
|
178
|
+
// Champs d'adresse pour les rendez-vous physiques
|
|
179
|
+
if (location !== undefined) updateData.location = location || null;
|
|
180
|
+
if (locationAddress !== undefined) updateData.locationAddress = locationAddress || null;
|
|
181
|
+
if (locationCity !== undefined) updateData.locationCity = locationCity || null;
|
|
182
|
+
if (locationPostalCode !== undefined)
|
|
183
|
+
updateData.locationPostalCode = locationPostalCode || null;
|
|
184
|
+
if (isAtHome !== undefined) updateData.isAtHome = isAtHome === true;
|
|
185
|
+
|
|
186
|
+
const canAssign = await checkPermission('tasks.assign');
|
|
187
|
+
if (assignedUserId !== undefined && canAssign) {
|
|
166
188
|
updateData.assignedUserId = assignedUserId;
|
|
167
189
|
}
|
|
168
190
|
|
|
169
191
|
// Si la tâche a un googleEventId, synchroniser avec Google Calendar
|
|
170
192
|
if (existingTask.googleEventId) {
|
|
171
193
|
try {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
});
|
|
194
|
+
const { getUserGoogleAccount } = await import('@/lib/google-calendar');
|
|
195
|
+
const googleAccount = await getUserGoogleAccount(session.user.id);
|
|
175
196
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
// Mettre à jour le token si nécessaire
|
|
184
|
-
if (accessToken !== googleAccount.accessToken) {
|
|
185
|
-
const tokenExpiresAt = new Date();
|
|
186
|
-
tokenExpiresAt.setSeconds(tokenExpiresAt.getSeconds() + 3600);
|
|
187
|
-
await prisma.userGoogleAccount.update({
|
|
188
|
-
where: { userId: session.user.id },
|
|
189
|
-
data: {
|
|
190
|
-
accessToken,
|
|
191
|
-
tokenExpiresAt,
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
}
|
|
197
|
+
const accessToken = await getValidAccessToken(
|
|
198
|
+
googleAccount.accessToken,
|
|
199
|
+
googleAccount.refreshToken,
|
|
200
|
+
googleAccount.tokenExpiresAt,
|
|
201
|
+
);
|
|
195
202
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
209
|
-
};
|
|
210
|
-
googleUpdate.end = {
|
|
211
|
-
dateTime: endDate.toISOString(),
|
|
212
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
203
|
+
// Mettre à jour le token si nécessaire
|
|
204
|
+
if (accessToken !== googleAccount.accessToken) {
|
|
205
|
+
const tokenExpiresAt = new Date();
|
|
206
|
+
tokenExpiresAt.setSeconds(tokenExpiresAt.getSeconds() + 3600);
|
|
207
|
+
await prisma.userGoogleAccount.update({
|
|
208
|
+
where: { userId: session.user.id },
|
|
209
|
+
data: {
|
|
210
|
+
accessToken: encrypt(accessToken),
|
|
211
|
+
tokenExpiresAt,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
});
|
|
216
|
+
// Préparer les données de mise à jour pour Google Calendar
|
|
217
|
+
const googleUpdate: any = {};
|
|
218
|
+
if (title !== undefined) googleUpdate.summary = title;
|
|
219
|
+
if (description !== undefined) googleUpdate.description = description;
|
|
220
|
+
if (location !== undefined) googleUpdate.location = location || undefined;
|
|
221
|
+
|
|
222
|
+
if (scheduledAt !== undefined) {
|
|
223
|
+
const startDate = new Date(scheduledAt);
|
|
224
|
+
const duration = durationMinutes || existingTask.durationMinutes || 30;
|
|
225
|
+
const endDate = new Date(startDate.getTime() + duration * 60 * 1000);
|
|
226
|
+
|
|
227
|
+
googleUpdate.start = {
|
|
228
|
+
dateTime: startDate.toISOString(),
|
|
229
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
230
|
+
};
|
|
231
|
+
googleUpdate.end = {
|
|
232
|
+
dateTime: endDate.toISOString(),
|
|
233
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
237
236
|
|
|
238
|
-
|
|
237
|
+
// Mettre à jour les invités si fournis
|
|
238
|
+
if (attendees !== undefined && Array.isArray(attendees)) {
|
|
239
|
+
// Récupérer le contact pour l'inclure dans la liste
|
|
240
|
+
const contact = existingTask.contactId
|
|
241
|
+
? await prisma.contact.findUnique({
|
|
242
|
+
where: { id: existingTask.contactId },
|
|
243
|
+
select: { email: true },
|
|
244
|
+
})
|
|
245
|
+
: null;
|
|
246
|
+
|
|
247
|
+
// Construire la liste des invités (contact + invités additionnels)
|
|
248
|
+
const allAttendees = [];
|
|
249
|
+
if (contact?.email) {
|
|
250
|
+
allAttendees.push({ email: contact.email });
|
|
239
251
|
}
|
|
252
|
+
// Ajouter les autres invités (exclure le contact s'il est déjà dans la liste)
|
|
253
|
+
attendees.forEach((email: string) => {
|
|
254
|
+
if (email && email.trim() !== '' && email !== contact?.email) {
|
|
255
|
+
allAttendees.push({ email: email.trim() });
|
|
256
|
+
}
|
|
257
|
+
});
|
|
240
258
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
259
|
+
googleUpdate.attendees = allAttendees.length > 0 ? allAttendees : undefined;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Mettre à jour l'évènement Google Calendar
|
|
263
|
+
const updatedGoogleEvent = await updateGoogleCalendarEvent(
|
|
264
|
+
accessToken,
|
|
265
|
+
existingTask.googleEventId,
|
|
266
|
+
googleUpdate,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Mettre à jour le lien Meet si nécessaire
|
|
270
|
+
const meetLink = extractMeetLink(updatedGoogleEvent);
|
|
271
|
+
if (meetLink) {
|
|
272
|
+
updateData.googleMeetLink = meetLink;
|
|
253
273
|
}
|
|
254
274
|
} catch (googleError: any) {
|
|
255
275
|
console.error('Erreur lors de la synchronisation avec Google Calendar:', googleError);
|
|
@@ -353,11 +373,10 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
353
373
|
// Pour Google Meet uniquement, récupérer les invités depuis Google Calendar
|
|
354
374
|
if (existingTask.googleEventId && task.googleMeetLink) {
|
|
355
375
|
try {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
});
|
|
376
|
+
const { getUserGoogleAccount } = await import('@/lib/google-calendar');
|
|
377
|
+
const googleAccount = await getUserGoogleAccount(session.user.id);
|
|
359
378
|
|
|
360
|
-
if (
|
|
379
|
+
if (existingTask.googleEventId) {
|
|
361
380
|
const accessToken = await getValidAccessToken(
|
|
362
381
|
googleAccount.accessToken,
|
|
363
382
|
googleAccount.refreshToken,
|
|
@@ -465,6 +484,15 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
465
484
|
}
|
|
466
485
|
}
|
|
467
486
|
|
|
487
|
+
// Déclencher les workflows TASK_COMPLETED si la tâche passe à complétée et a un contact
|
|
488
|
+
if (completed === true && !existingTask.completed && task.contact?.id) {
|
|
489
|
+
try {
|
|
490
|
+
await executeWorkflowsOnTaskCompleted(task.contact.id, existingTask.type);
|
|
491
|
+
} catch (workflowError) {
|
|
492
|
+
console.error("Erreur lors de l'exécution des workflows:", workflowError);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
468
496
|
return NextResponse.json(task);
|
|
469
497
|
} catch (error: any) {
|
|
470
498
|
console.error('Erreur lors de la mise à jour de la tâche:', error);
|
|
@@ -486,11 +514,15 @@ export async function DELETE(
|
|
|
486
514
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
487
515
|
}
|
|
488
516
|
|
|
517
|
+
const canDelete = await checkPermission('tasks.delete');
|
|
518
|
+
if (!canDelete) {
|
|
519
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
520
|
+
}
|
|
521
|
+
|
|
489
522
|
const { id } = await params;
|
|
490
523
|
const body = await request.json().catch(() => ({}));
|
|
491
524
|
const { notifyContact } = body;
|
|
492
525
|
|
|
493
|
-
// Vérifier que la tâche existe
|
|
494
526
|
const task = await prisma.task.findUnique({
|
|
495
527
|
where: { id },
|
|
496
528
|
});
|
|
@@ -499,13 +531,8 @@ export async function DELETE(
|
|
|
499
531
|
return NextResponse.json({ error: 'Tâche non trouvée' }, { status: 404 });
|
|
500
532
|
}
|
|
501
533
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
where: { id: session.user.id },
|
|
505
|
-
select: { role: true },
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
if (task.assignedUserId !== session.user.id && user?.role !== 'ADMIN') {
|
|
534
|
+
const canEditAll = await checkPermission('tasks.edit_all');
|
|
535
|
+
if (task.assignedUserId !== session.user.id && !canEditAll) {
|
|
509
536
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
510
537
|
}
|
|
511
538
|
|
|
@@ -544,43 +571,40 @@ export async function DELETE(
|
|
|
544
571
|
allRecipients.push(taskWithContact.contact.email!);
|
|
545
572
|
|
|
546
573
|
try {
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
});
|
|
574
|
+
const { getUserGoogleAccount } = await import('@/lib/google-calendar');
|
|
575
|
+
const googleAccount = await getUserGoogleAccount(session.user.id);
|
|
550
576
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
);
|
|
557
|
-
|
|
558
|
-
// Mettre à jour le token si nécessaire
|
|
559
|
-
if (accessToken !== googleAccount.accessToken) {
|
|
560
|
-
const tokenExpiresAt = new Date();
|
|
561
|
-
tokenExpiresAt.setSeconds(tokenExpiresAt.getSeconds() + 3600);
|
|
562
|
-
await prisma.userGoogleAccount.update({
|
|
563
|
-
where: { userId: session.user.id },
|
|
564
|
-
data: {
|
|
565
|
-
accessToken,
|
|
566
|
-
tokenExpiresAt,
|
|
567
|
-
},
|
|
568
|
-
});
|
|
569
|
-
}
|
|
577
|
+
const accessToken = await getValidAccessToken(
|
|
578
|
+
googleAccount.accessToken,
|
|
579
|
+
googleAccount.refreshToken,
|
|
580
|
+
googleAccount.tokenExpiresAt,
|
|
581
|
+
);
|
|
570
582
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
583
|
+
// Mettre à jour le token si nécessaire
|
|
584
|
+
if (accessToken !== googleAccount.accessToken) {
|
|
585
|
+
const tokenExpiresAt = new Date();
|
|
586
|
+
tokenExpiresAt.setSeconds(tokenExpiresAt.getSeconds() + 3600);
|
|
587
|
+
await prisma.userGoogleAccount.update({
|
|
588
|
+
where: { userId: session.user.id },
|
|
589
|
+
data: {
|
|
590
|
+
accessToken: encrypt(accessToken),
|
|
591
|
+
tokenExpiresAt,
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
}
|
|
580
595
|
|
|
581
|
-
|
|
582
|
-
|
|
596
|
+
// Récupérer les invités AVANT de supprimer l'événement
|
|
597
|
+
const googleEvent = await getGoogleCalendarEvent(accessToken, task.googleEventId);
|
|
598
|
+
if (googleEvent.attendees) {
|
|
599
|
+
googleEvent.attendees.forEach((attendee) => {
|
|
600
|
+
if (attendee.email && !allRecipients.includes(attendee.email)) {
|
|
601
|
+
allRecipients.push(attendee.email);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
583
604
|
}
|
|
605
|
+
|
|
606
|
+
// Supprimer l'événement Google Calendar
|
|
607
|
+
await deleteGoogleCalendarEvent(accessToken, task.googleEventId);
|
|
584
608
|
} catch (googleError: any) {
|
|
585
609
|
console.error("Erreur lors de la suppression de l'événement Google Calendar:", googleError);
|
|
586
610
|
// On continue quand même la suppression de la tâche
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '@/lib/google-calendar';
|
|
9
9
|
import { createInteraction } from '@/lib/contact-interactions';
|
|
10
10
|
import nodemailer from 'nodemailer';
|
|
11
|
-
import { decrypt } from '@/lib/encryption';
|
|
11
|
+
import { decrypt, encrypt } from '@/lib/encryption';
|
|
12
12
|
import { render } from '@react-email/render';
|
|
13
13
|
import { MeetConfirmationEmailTemplate } from '@/components/meet-confirmation-email-template';
|
|
14
14
|
import React from 'react';
|
|
@@ -58,13 +58,16 @@ export async function POST(request: NextRequest) {
|
|
|
58
58
|
// Vérifier que l'utilisateur a un compte Google connecté seulement si on veut ajouter à Google Calendar
|
|
59
59
|
let googleAccount = null;
|
|
60
60
|
if (addToGoogleCalendar) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (!googleAccount) {
|
|
61
|
+
try {
|
|
62
|
+
const { getUserGoogleAccount } = await import('@/lib/google-calendar');
|
|
63
|
+
googleAccount = await getUserGoogleAccount(session.user.id);
|
|
64
|
+
} catch (error: any) {
|
|
66
65
|
return NextResponse.json(
|
|
67
|
-
{
|
|
66
|
+
{
|
|
67
|
+
error:
|
|
68
|
+
error.message ||
|
|
69
|
+
'Veuillez connecter votre compte Google dans les paramètres pour utiliser Google Calendar.',
|
|
70
|
+
},
|
|
68
71
|
{ status: 400 },
|
|
69
72
|
);
|
|
70
73
|
}
|
|
@@ -99,7 +102,7 @@ export async function POST(request: NextRequest) {
|
|
|
99
102
|
await prisma.userGoogleAccount.update({
|
|
100
103
|
where: { userId: session.user.id },
|
|
101
104
|
data: {
|
|
102
|
-
accessToken,
|
|
105
|
+
accessToken: encrypt(accessToken),
|
|
103
106
|
tokenExpiresAt,
|
|
104
107
|
},
|
|
105
108
|
});
|