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,9 +1,9 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { auth } from '@/lib/auth';
|
|
3
2
|
import { prisma } from '@/lib/prisma';
|
|
3
|
+
import { getAuthUser } from '@/lib/get-auth-user';
|
|
4
4
|
import { logAppointmentCreated, createInteraction } from '@/lib/contact-interactions';
|
|
5
5
|
import nodemailer from 'nodemailer';
|
|
6
|
-
import { decrypt } from '@/lib/encryption';
|
|
6
|
+
import { decrypt, encrypt } from '@/lib/encryption';
|
|
7
7
|
import { render } from '@react-email/render';
|
|
8
8
|
import { MeetConfirmationEmailTemplate } from '@/components/meet-confirmation-email-template';
|
|
9
9
|
import React from 'react';
|
|
@@ -22,62 +22,81 @@ function htmlToText(html: string): string {
|
|
|
22
22
|
// GET /api/tasks - Récupérer les tâches de l'utilisateur
|
|
23
23
|
export async function GET(request: NextRequest) {
|
|
24
24
|
try {
|
|
25
|
-
const
|
|
26
|
-
headers: request.headers,
|
|
27
|
-
});
|
|
25
|
+
const authUser = await getAuthUser();
|
|
28
26
|
|
|
29
|
-
if (!
|
|
27
|
+
if (!authUser) {
|
|
30
28
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
31
29
|
}
|
|
32
30
|
|
|
31
|
+
const { session, permissions } = authUser;
|
|
32
|
+
const canViewAll = permissions.includes('tasks.view_all');
|
|
33
|
+
const canViewOwn = permissions.includes('tasks.view_own');
|
|
34
|
+
|
|
35
|
+
if (!canViewAll && !canViewOwn) {
|
|
36
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
const { searchParams } = new URL(request.url);
|
|
34
40
|
const startDate = searchParams.get('startDate');
|
|
35
41
|
const endDate = searchParams.get('endDate');
|
|
36
|
-
const assignedTo = searchParams.get('assignedTo');
|
|
37
|
-
const contactId = searchParams.get('contactId');
|
|
42
|
+
const assignedTo = searchParams.get('assignedTo');
|
|
43
|
+
const contactId = searchParams.get('contactId');
|
|
38
44
|
const showOtherUsers = searchParams.get('showOtherUsers') === 'true';
|
|
39
45
|
|
|
40
|
-
// Construire les filtres
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
// Construire les filtres de base
|
|
47
|
+
// Pour les événements multi-jours, on récupère les tâches avec une marge
|
|
48
|
+
// Les tâches qui commencent jusqu'à 30 jours avant le début de la période
|
|
49
|
+
// peuvent potentiellement être affichées (événements longs)
|
|
50
|
+
const baseFilters: any = {};
|
|
51
|
+
|
|
52
|
+
if (startDate) {
|
|
53
|
+
const startDateObj = new Date(startDate);
|
|
54
|
+
// Marge de 30 jours avant pour capturer les événements longs
|
|
55
|
+
const marginStart = new Date(startDateObj);
|
|
56
|
+
marginStart.setDate(marginStart.getDate() - 30);
|
|
57
|
+
|
|
58
|
+
baseFilters.scheduledAt = {
|
|
59
|
+
gte: marginStart,
|
|
44
60
|
lte: endDate ? new Date(endDate) : undefined,
|
|
45
|
-
}
|
|
46
|
-
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!endDate) {
|
|
64
|
+
delete baseFilters.scheduledAt.lte;
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
baseFilters.scheduledAt = {
|
|
68
|
+
gte: new Date(),
|
|
69
|
+
lte: endDate ? new Date(endDate) : undefined,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (!endDate) {
|
|
73
|
+
delete baseFilters.scheduledAt.lte;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
47
76
|
|
|
48
77
|
// Filtrer par contact si fourni
|
|
49
78
|
if (contactId) {
|
|
50
|
-
|
|
79
|
+
baseFilters.contactId = contactId;
|
|
51
80
|
}
|
|
52
81
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// Pas de permission, voir uniquement ses propres tâches
|
|
62
|
-
where.assignedUserId = session.user.id;
|
|
63
|
-
}
|
|
64
|
-
} else if (assignedTo && assignedTo !== session.user.id) {
|
|
65
|
-
const { checkPermission } = await import('@/lib/check-permission');
|
|
66
|
-
const canViewOthers = await checkPermission('tasks.view_other_users_events');
|
|
67
|
-
if (canViewOthers) {
|
|
68
|
-
where.assignedUserId = assignedTo;
|
|
69
|
-
} else {
|
|
70
|
-
// Pas de permission, voir uniquement ses propres tâches
|
|
71
|
-
where.assignedUserId = session.user.id;
|
|
82
|
+
// Construire les filtres d'utilisateur basés sur les permissions
|
|
83
|
+
let userFilters: any = {};
|
|
84
|
+
|
|
85
|
+
if (showOtherUsers && permissions.includes('tasks.view_other_users_events')) {
|
|
86
|
+
userFilters = {};
|
|
87
|
+
} else if (canViewAll) {
|
|
88
|
+
if (assignedTo) {
|
|
89
|
+
userFilters = { assignedUserId: assignedTo };
|
|
72
90
|
}
|
|
73
91
|
} else {
|
|
74
|
-
|
|
75
|
-
where.assignedUserId = session.user.id;
|
|
92
|
+
userFilters = { assignedUserId: session.user.id };
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
// Combiner tous les filtres
|
|
96
|
+
const where: any = {
|
|
97
|
+
...baseFilters,
|
|
98
|
+
...userFilters,
|
|
99
|
+
};
|
|
81
100
|
|
|
82
101
|
const tasks = await prisma.task.findMany({
|
|
83
102
|
where,
|
|
@@ -96,7 +115,6 @@ export async function GET(request: NextRequest) {
|
|
|
96
115
|
id: true,
|
|
97
116
|
name: true,
|
|
98
117
|
email: true,
|
|
99
|
-
eventColor: true,
|
|
100
118
|
},
|
|
101
119
|
},
|
|
102
120
|
createdBy: {
|
|
@@ -115,22 +133,32 @@ export async function GET(request: NextRequest) {
|
|
|
115
133
|
return NextResponse.json(tasks);
|
|
116
134
|
} catch (error: any) {
|
|
117
135
|
console.error('Erreur lors de la récupération des tâches:', error);
|
|
118
|
-
return NextResponse.json(
|
|
136
|
+
return NextResponse.json(
|
|
137
|
+
{
|
|
138
|
+
error:
|
|
139
|
+
process.env.NODE_ENV === 'development'
|
|
140
|
+
? error.message || 'Erreur serveur'
|
|
141
|
+
: 'Erreur serveur',
|
|
142
|
+
},
|
|
143
|
+
{ status: 500 },
|
|
144
|
+
);
|
|
119
145
|
}
|
|
120
146
|
}
|
|
121
147
|
|
|
122
148
|
// POST /api/tasks - Créer une nouvelle tâche
|
|
123
149
|
export async function POST(request: NextRequest) {
|
|
124
150
|
try {
|
|
125
|
-
const
|
|
126
|
-
headers: request.headers,
|
|
127
|
-
});
|
|
151
|
+
const [authUser, body] = await Promise.all([getAuthUser(), request.json()]);
|
|
128
152
|
|
|
129
|
-
if (!
|
|
153
|
+
if (!authUser) {
|
|
130
154
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
131
155
|
}
|
|
132
156
|
|
|
133
|
-
const
|
|
157
|
+
const { session } = authUser;
|
|
158
|
+
|
|
159
|
+
if (!authUser.permissions.includes('tasks.create')) {
|
|
160
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
161
|
+
}
|
|
134
162
|
const {
|
|
135
163
|
type,
|
|
136
164
|
title,
|
|
@@ -143,6 +171,12 @@ export async function POST(request: NextRequest) {
|
|
|
143
171
|
internalNote,
|
|
144
172
|
attendees = [],
|
|
145
173
|
addToGoogleCalendar = true,
|
|
174
|
+
// Champs pour les rendez-vous physiques
|
|
175
|
+
location,
|
|
176
|
+
locationAddress,
|
|
177
|
+
locationCity,
|
|
178
|
+
locationPostalCode,
|
|
179
|
+
isAtHome = false,
|
|
146
180
|
} = body;
|
|
147
181
|
|
|
148
182
|
// Validation
|
|
@@ -153,20 +187,20 @@ export async function POST(request: NextRequest) {
|
|
|
153
187
|
);
|
|
154
188
|
}
|
|
155
189
|
|
|
190
|
+
// Vérifier si l'utilisateur est admin
|
|
191
|
+
const user = await prisma.user.findUnique({
|
|
192
|
+
where: { id: session.user.id },
|
|
193
|
+
select: { role: true },
|
|
194
|
+
});
|
|
195
|
+
|
|
156
196
|
// Déterminer l'utilisateur assigné
|
|
157
197
|
let finalAssignedUserId: string;
|
|
158
|
-
if (assignedUserId &&
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (canAssignOthers) {
|
|
162
|
-
// Utilisateur avec permission peut assigner à n'importe qui
|
|
163
|
-
finalAssignedUserId = assignedUserId;
|
|
164
|
-
} else {
|
|
165
|
-
// Pas de permission, s'assigne automatiquement
|
|
166
|
-
finalAssignedUserId = session.user.id;
|
|
167
|
-
}
|
|
198
|
+
if (assignedUserId && user?.role === 'ADMIN') {
|
|
199
|
+
// Admin peut assigner à n'importe qui
|
|
200
|
+
finalAssignedUserId = assignedUserId;
|
|
168
201
|
} else {
|
|
169
|
-
|
|
202
|
+
// Utilisateur normal s'assigne automatiquement
|
|
203
|
+
finalAssignedUserId = session.user.id;
|
|
170
204
|
}
|
|
171
205
|
|
|
172
206
|
// Vérifier que le contact existe si fourni
|
|
@@ -193,6 +227,12 @@ export async function POST(request: NextRequest) {
|
|
|
193
227
|
typeof reminderMinutesBefore === 'number' ? reminderMinutesBefore : null,
|
|
194
228
|
notifyContact: notifyContact === true,
|
|
195
229
|
internalNote: internalNote || null,
|
|
230
|
+
// Champs d'adresse pour les rendez-vous physiques
|
|
231
|
+
location: location || null,
|
|
232
|
+
locationAddress: locationAddress || null,
|
|
233
|
+
locationCity: locationCity || null,
|
|
234
|
+
locationPostalCode: locationPostalCode || null,
|
|
235
|
+
isAtHome: isAtHome === true,
|
|
196
236
|
},
|
|
197
237
|
include: {
|
|
198
238
|
contact: {
|
|
@@ -209,7 +249,6 @@ export async function POST(request: NextRequest) {
|
|
|
209
249
|
id: true,
|
|
210
250
|
name: true,
|
|
211
251
|
email: true,
|
|
212
|
-
eventColor: true,
|
|
213
252
|
},
|
|
214
253
|
},
|
|
215
254
|
createdBy: {
|
|
@@ -241,44 +280,55 @@ export async function POST(request: NextRequest) {
|
|
|
241
280
|
// Si c'est un rendez-vous (physique) ou une tâche et que l'utilisateur veut l'ajouter à Google Calendar
|
|
242
281
|
if ((type === 'MEETING' || type === 'TASK') && addToGoogleCalendar) {
|
|
243
282
|
try {
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const googleEvent = await createGoogleCalendarEvent(accessToken, {
|
|
261
|
-
summary: title || (type === 'MEETING' ? 'Rendez-vous' : 'Tâche'),
|
|
262
|
-
description: htmlToText(description),
|
|
263
|
-
start: {
|
|
264
|
-
dateTime: startDate.toISOString(),
|
|
265
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
266
|
-
},
|
|
267
|
-
end: {
|
|
268
|
-
dateTime: endDate.toISOString(),
|
|
269
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
270
|
-
},
|
|
271
|
-
attendees: allAttendees.length > 0 ? allAttendees : undefined,
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// Sauvegarder l'ID de l'évènement pour synchroniser les modifications/suppressions
|
|
275
|
-
await prisma.task.update({
|
|
276
|
-
where: { id: task.id },
|
|
283
|
+
const { getUserGoogleAccount } = await import('@/lib/google-calendar');
|
|
284
|
+
const googleAccount = await getUserGoogleAccount(session.user.id);
|
|
285
|
+
|
|
286
|
+
const accessToken = await getValidAccessToken(
|
|
287
|
+
googleAccount.accessToken,
|
|
288
|
+
googleAccount.refreshToken,
|
|
289
|
+
googleAccount.tokenExpiresAt,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// Mettre à jour le token si nécessaire
|
|
293
|
+
if (accessToken !== googleAccount.accessToken) {
|
|
294
|
+
const tokenExpiresAt = new Date();
|
|
295
|
+
tokenExpiresAt.setSeconds(tokenExpiresAt.getSeconds() + 3600);
|
|
296
|
+
await prisma.userGoogleAccount.update({
|
|
297
|
+
where: { userId: session.user.id },
|
|
277
298
|
data: {
|
|
278
|
-
|
|
299
|
+
accessToken: encrypt(accessToken),
|
|
300
|
+
tokenExpiresAt,
|
|
279
301
|
},
|
|
280
302
|
});
|
|
281
303
|
}
|
|
304
|
+
|
|
305
|
+
// Durée par défaut de 60 minutes pour les rendez-vous physiques, 30 minutes pour les tâches
|
|
306
|
+
const startDate = new Date(scheduledAt);
|
|
307
|
+
const duration = type === 'MEETING' ? 60 : 30;
|
|
308
|
+
const endDate = new Date(startDate.getTime() + duration * 60 * 1000);
|
|
309
|
+
|
|
310
|
+
const googleEvent = await createGoogleCalendarEvent(accessToken, {
|
|
311
|
+
summary: title || (type === 'MEETING' ? 'Rendez-vous' : 'Tâche'),
|
|
312
|
+
description: htmlToText(description),
|
|
313
|
+
start: {
|
|
314
|
+
dateTime: startDate.toISOString(),
|
|
315
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
316
|
+
},
|
|
317
|
+
end: {
|
|
318
|
+
dateTime: endDate.toISOString(),
|
|
319
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
320
|
+
},
|
|
321
|
+
attendees: allAttendees.length > 0 ? allAttendees : undefined,
|
|
322
|
+
location: location || undefined, // Ajouter l'adresse du rendez-vous
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Sauvegarder l'ID de l'évènement pour synchroniser les modifications/suppressions
|
|
326
|
+
await prisma.task.update({
|
|
327
|
+
where: { id: task.id },
|
|
328
|
+
data: {
|
|
329
|
+
googleEventId: googleEvent.id,
|
|
330
|
+
},
|
|
331
|
+
});
|
|
282
332
|
} catch (googleError: any) {
|
|
283
333
|
console.error('Erreur lors de la création de lévènement Google Calendar:', googleError);
|
|
284
334
|
// On ne bloque pas la création de la tâche si Google Calendar échoue
|
|
@@ -299,7 +349,8 @@ export async function POST(request: NextRequest) {
|
|
|
299
349
|
);
|
|
300
350
|
|
|
301
351
|
// Envoyer un email de notification si demandé (contact ou invités)
|
|
302
|
-
|
|
352
|
+
// Vérifier explicitement que notifyContact est true
|
|
353
|
+
if (notifyContact === true) {
|
|
303
354
|
try {
|
|
304
355
|
// Récupérer la configuration SMTP
|
|
305
356
|
const smtpConfig = await prisma.smtpConfig.findUnique({
|
|
@@ -312,6 +363,7 @@ export async function POST(request: NextRequest) {
|
|
|
312
363
|
try {
|
|
313
364
|
password = decrypt(smtpConfig.password);
|
|
314
365
|
} catch (error) {
|
|
366
|
+
console.error(error);
|
|
315
367
|
password = smtpConfig.password;
|
|
316
368
|
}
|
|
317
369
|
|
|
@@ -378,7 +430,7 @@ export async function POST(request: NextRequest) {
|
|
|
378
430
|
? `"${smtpConfig.fromName}" <${smtpConfig.fromEmail}>`
|
|
379
431
|
: smtpConfig.fromEmail,
|
|
380
432
|
to: recipient.email,
|
|
381
|
-
subject: `Confirmation de rendez-vous${title ?
|
|
433
|
+
subject: `Confirmation de rendez-vous ${title ? `: ${title}` : ''}`,
|
|
382
434
|
text: emailText,
|
|
383
435
|
html: emailHtml,
|
|
384
436
|
});
|
|
@@ -416,6 +468,14 @@ export async function POST(request: NextRequest) {
|
|
|
416
468
|
return NextResponse.json(task, { status: 201 });
|
|
417
469
|
} catch (error: any) {
|
|
418
470
|
console.error('Erreur lors de la création de la tâche:', error);
|
|
419
|
-
return NextResponse.json(
|
|
471
|
+
return NextResponse.json(
|
|
472
|
+
{
|
|
473
|
+
error:
|
|
474
|
+
process.env.NODE_ENV === 'development'
|
|
475
|
+
? error.message || 'Erreur serveur'
|
|
476
|
+
: 'Erreur serveur',
|
|
477
|
+
},
|
|
478
|
+
{ status: 500 },
|
|
479
|
+
);
|
|
420
480
|
}
|
|
421
481
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
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';
|
|
4
5
|
|
|
5
|
-
// GET /api/templates/[id] - Récupérer un template spécifique
|
|
6
|
+
// GET /api/templates/[id] - Récupérer un template spécifique (commun à tous les utilisateurs)
|
|
6
7
|
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
7
8
|
try {
|
|
8
9
|
const session = await auth.api.getSession({
|
|
@@ -13,12 +14,16 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
13
14
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
const canView = await checkPermission('templates.view');
|
|
18
|
+
if (!canView) {
|
|
19
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
const { id } = await params;
|
|
17
23
|
|
|
18
|
-
const template = await prisma.template.
|
|
24
|
+
const template = await prisma.template.findUnique({
|
|
19
25
|
where: {
|
|
20
26
|
id,
|
|
21
|
-
userId: session.user.id,
|
|
22
27
|
},
|
|
23
28
|
});
|
|
24
29
|
|
|
@@ -33,7 +38,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
// PUT /api/templates/[id] - Mettre à jour un template
|
|
41
|
+
// PUT /api/templates/[id] - Mettre à jour un template (commun à tous les utilisateurs)
|
|
37
42
|
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
38
43
|
try {
|
|
39
44
|
const session = await auth.api.getSession({
|
|
@@ -44,15 +49,18 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
44
49
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
45
50
|
}
|
|
46
51
|
|
|
52
|
+
const canEdit = await checkPermission('templates.edit');
|
|
53
|
+
if (!canEdit) {
|
|
54
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
const { id } = await params;
|
|
48
58
|
const body = await request.json();
|
|
49
59
|
const { name, type, subject, content } = body;
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
const existing = await prisma.template.findFirst({
|
|
61
|
+
const existing = await prisma.template.findUnique({
|
|
53
62
|
where: {
|
|
54
63
|
id,
|
|
55
|
-
userId: session.user.id,
|
|
56
64
|
},
|
|
57
65
|
});
|
|
58
66
|
|
|
@@ -60,7 +68,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
60
68
|
return NextResponse.json({ error: 'Template non trouvé' }, { status: 404 });
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
// Validation
|
|
64
71
|
if (!name || !type || !content) {
|
|
65
72
|
return NextResponse.json(
|
|
66
73
|
{ error: 'Le nom, le type et le contenu sont requis' },
|
|
@@ -75,7 +82,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
75
82
|
);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
// Pour EMAIL, le sujet est requis
|
|
79
85
|
if (type === 'EMAIL' && !subject) {
|
|
80
86
|
return NextResponse.json(
|
|
81
87
|
{ error: 'Le sujet est requis pour les templates EMAIL' },
|
|
@@ -100,7 +106,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
// DELETE /api/templates/[id] - Supprimer un template
|
|
109
|
+
// DELETE /api/templates/[id] - Supprimer un template (commun à tous les utilisateurs)
|
|
104
110
|
export async function DELETE(
|
|
105
111
|
request: NextRequest,
|
|
106
112
|
{ params }: { params: Promise<{ id: string }> },
|
|
@@ -114,13 +120,16 @@ export async function DELETE(
|
|
|
114
120
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
115
121
|
}
|
|
116
122
|
|
|
123
|
+
const canDelete = await checkPermission('templates.delete');
|
|
124
|
+
if (!canDelete) {
|
|
125
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
const { id } = await params;
|
|
118
129
|
|
|
119
|
-
|
|
120
|
-
const existing = await prisma.template.findFirst({
|
|
130
|
+
const existing = await prisma.template.findUnique({
|
|
121
131
|
where: {
|
|
122
132
|
id,
|
|
123
|
-
userId: session.user.id,
|
|
124
133
|
},
|
|
125
134
|
});
|
|
126
135
|
|
|
@@ -1,8 +1,9 @@
|
|
|
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';
|
|
4
5
|
|
|
5
|
-
// GET /api/templates - Récupérer tous les templates
|
|
6
|
+
// GET /api/templates - Récupérer tous les templates (communs à tous les utilisateurs)
|
|
6
7
|
export async function GET(request: NextRequest) {
|
|
7
8
|
try {
|
|
8
9
|
const session = await auth.api.getSession({
|
|
@@ -13,12 +14,15 @@ export async function GET(request: NextRequest) {
|
|
|
13
14
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
const canView = await checkPermission('templates.view');
|
|
18
|
+
if (!canView) {
|
|
19
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
const { searchParams } = new URL(request.url);
|
|
17
23
|
const type = searchParams.get('type'); // EMAIL, SMS, NOTE
|
|
18
24
|
|
|
19
|
-
const where: any = {
|
|
20
|
-
userId: session.user.id,
|
|
21
|
-
};
|
|
25
|
+
const where: any = {};
|
|
22
26
|
|
|
23
27
|
if (type) {
|
|
24
28
|
where.type = type;
|
|
@@ -47,6 +51,11 @@ export async function POST(request: NextRequest) {
|
|
|
47
51
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
const canCreate = await checkPermission('templates.create');
|
|
55
|
+
if (!canCreate) {
|
|
56
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
const body = await request.json();
|
|
51
60
|
const { name, type, subject, content } = body;
|
|
52
61
|
|
|
@@ -86,6 +95,14 @@ export async function POST(request: NextRequest) {
|
|
|
86
95
|
return NextResponse.json(template, { status: 201 });
|
|
87
96
|
} catch (error: any) {
|
|
88
97
|
console.error('Erreur lors de la création du template:', error);
|
|
89
|
-
return NextResponse.json(
|
|
98
|
+
return NextResponse.json(
|
|
99
|
+
{
|
|
100
|
+
error:
|
|
101
|
+
process.env.NODE_ENV === 'development'
|
|
102
|
+
? error.message || 'Erreur serveur'
|
|
103
|
+
: 'Erreur serveur',
|
|
104
|
+
},
|
|
105
|
+
{ status: 500 },
|
|
106
|
+
);
|
|
90
107
|
}
|
|
91
108
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { prisma } from '@/lib/prisma';
|
|
3
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
4
|
+
import { auth } from '@/lib/auth';
|
|
5
|
+
|
|
6
|
+
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
7
|
+
try {
|
|
8
|
+
const [session, { id }] = await Promise.all([
|
|
9
|
+
auth.api.getSession({ headers: request.headers }),
|
|
10
|
+
params,
|
|
11
|
+
]);
|
|
12
|
+
if (!session) {
|
|
13
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const hasPermission = await checkPermission('users.create');
|
|
17
|
+
if (!hasPermission) {
|
|
18
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const user = await prisma.user.findUnique({
|
|
22
|
+
where: { id },
|
|
23
|
+
include: {
|
|
24
|
+
accounts: {
|
|
25
|
+
where: { providerId: 'credential' },
|
|
26
|
+
select: { id: true },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!user) {
|
|
32
|
+
return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (user.accounts.length > 0 || user.emailVerified) {
|
|
36
|
+
return NextResponse.json(
|
|
37
|
+
{ error: 'Cet utilisateur a déjà complété son inscription' },
|
|
38
|
+
{ status: 400 },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const token = crypto.randomUUID();
|
|
43
|
+
const expiresAt = new Date();
|
|
44
|
+
expiresAt.setDate(expiresAt.getDate() + 1);
|
|
45
|
+
|
|
46
|
+
await prisma.$transaction([
|
|
47
|
+
prisma.verification.deleteMany({
|
|
48
|
+
where: { identifier: user.email },
|
|
49
|
+
}),
|
|
50
|
+
prisma.verification.create({
|
|
51
|
+
data: {
|
|
52
|
+
id: crypto.randomUUID(),
|
|
53
|
+
identifier: user.email,
|
|
54
|
+
value: token,
|
|
55
|
+
expiresAt,
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const baseUrl = process.env.BETTER_AUTH_URL || 'http://localhost:3000';
|
|
61
|
+
const invitationUrl = `${baseUrl}/invite/${token}`;
|
|
62
|
+
|
|
63
|
+
const cookieHeader = request.headers.get('cookie') || '';
|
|
64
|
+
const emailResponse = await fetch(`${baseUrl}/api/send`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
Cookie: cookieHeader,
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
to: user.email,
|
|
72
|
+
subject: 'Invitation à rejoindre le CRM',
|
|
73
|
+
template: 'invitation',
|
|
74
|
+
invitationUrl,
|
|
75
|
+
name: user.name,
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!emailResponse.ok) {
|
|
80
|
+
const errorData = await emailResponse.json().catch(() => ({}));
|
|
81
|
+
console.error("Erreur lors de l'envoi de l'email de réinvitation:", errorData);
|
|
82
|
+
return NextResponse.json(
|
|
83
|
+
{ error: errorData.error || "Erreur lors de l'envoi de l'email" },
|
|
84
|
+
{ status: 500 },
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return NextResponse.json({
|
|
89
|
+
message: "Email d'invitation renvoyé avec succès",
|
|
90
|
+
});
|
|
91
|
+
} catch (error: any) {
|
|
92
|
+
console.error("Erreur lors du renvoi de l'invitation:", error);
|
|
93
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -3,6 +3,7 @@ import { prisma } from '@/lib/prisma';
|
|
|
3
3
|
import { checkPermission } from '@/lib/check-permission';
|
|
4
4
|
import { auth } from '@/lib/auth';
|
|
5
5
|
import { logAudit } from '@/lib/audit-log';
|
|
6
|
+
import { resolveRoleFromCustomRoleName } from '@/lib/roles';
|
|
6
7
|
|
|
7
8
|
// GET /api/users/[id] - Récupérer un utilisateur spécifique
|
|
8
9
|
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -86,12 +87,26 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
86
87
|
return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
+
// Résoudre le rôle enum si le profil change
|
|
91
|
+
let resolvedRole: string | undefined;
|
|
92
|
+
if (customRoleId !== undefined) {
|
|
93
|
+
if (customRoleId) {
|
|
94
|
+
const customRole = await prisma.customRole.findUnique({
|
|
95
|
+
where: { id: customRoleId },
|
|
96
|
+
select: { name: true },
|
|
97
|
+
});
|
|
98
|
+
resolvedRole = resolveRoleFromCustomRoleName(customRole?.name);
|
|
99
|
+
} else {
|
|
100
|
+
resolvedRole = 'USER';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
const updatedUser = await prisma.user.update({
|
|
91
105
|
where: { id },
|
|
92
106
|
data: {
|
|
93
107
|
...(name && { name }),
|
|
94
108
|
...(customRoleId !== undefined && { customRoleId: customRoleId || null }),
|
|
109
|
+
...(resolvedRole && { role: resolvedRole as any }),
|
|
95
110
|
...(typeof active === 'boolean' && { active }),
|
|
96
111
|
},
|
|
97
112
|
include: {
|