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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { auth } from '@/lib/auth';
|
|
4
|
+
import { prisma } from '@/lib/prisma';
|
|
5
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
6
|
+
|
|
7
|
+
const createCompanySchema = z.object({
|
|
8
|
+
name: z.string().trim().min(1, 'Le nom est obligatoire'),
|
|
9
|
+
phone: z.string().trim().optional().nullable(),
|
|
10
|
+
email: z.string().email().optional().nullable(),
|
|
11
|
+
address: z.string().optional().nullable(),
|
|
12
|
+
city: z.string().optional().nullable(),
|
|
13
|
+
postalCode: z.string().optional().nullable(),
|
|
14
|
+
website: z.string().optional().nullable(),
|
|
15
|
+
siret: z.string().optional().nullable(),
|
|
16
|
+
industry: z.string().optional().nullable(),
|
|
17
|
+
notes: z.string().optional().nullable(),
|
|
18
|
+
assignedCommercialId: z.string().optional().nullable(),
|
|
19
|
+
assignedTeleproId: z.string().optional().nullable(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export async function GET(request: NextRequest) {
|
|
23
|
+
try {
|
|
24
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
25
|
+
if (!session) {
|
|
26
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const [companiesViewAll, companiesViewOwn, contactsViewAll, contactsViewOwn] =
|
|
30
|
+
await Promise.all([
|
|
31
|
+
checkPermission('companies.view_all'),
|
|
32
|
+
checkPermission('companies.view_own'),
|
|
33
|
+
checkPermission('contacts.view_all'),
|
|
34
|
+
checkPermission('contacts.view_own'),
|
|
35
|
+
]);
|
|
36
|
+
const canViewAll = companiesViewAll || contactsViewAll;
|
|
37
|
+
const canViewOwn = companiesViewOwn || contactsViewOwn;
|
|
38
|
+
|
|
39
|
+
if (!canViewAll && !canViewOwn) {
|
|
40
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { searchParams } = new URL(request.url);
|
|
44
|
+
const search = searchParams.get('search') || '';
|
|
45
|
+
const page = Number.parseInt(searchParams.get('page') || '1');
|
|
46
|
+
const limit = Number.parseInt(searchParams.get('limit') || '50');
|
|
47
|
+
const all = searchParams.get('all') === 'true';
|
|
48
|
+
const skip = (page - 1) * limit;
|
|
49
|
+
|
|
50
|
+
const where: any = {};
|
|
51
|
+
|
|
52
|
+
if (!canViewAll && canViewOwn) {
|
|
53
|
+
where.OR = [
|
|
54
|
+
{ assignedCommercialId: session.user.id },
|
|
55
|
+
{ assignedTeleproId: session.user.id },
|
|
56
|
+
{ createdById: session.user.id },
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (search) {
|
|
61
|
+
where.AND = where.AND || [];
|
|
62
|
+
where.AND.push({
|
|
63
|
+
OR: [
|
|
64
|
+
{ name: { contains: search, mode: 'insensitive' } },
|
|
65
|
+
{ email: { contains: search, mode: 'insensitive' } },
|
|
66
|
+
{ phone: { contains: search, mode: 'insensitive' } },
|
|
67
|
+
{ siret: { contains: search, mode: 'insensitive' } },
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (all) {
|
|
73
|
+
const companies = await prisma.company.findMany({
|
|
74
|
+
where,
|
|
75
|
+
select: { id: true, name: true },
|
|
76
|
+
orderBy: { name: 'asc' },
|
|
77
|
+
});
|
|
78
|
+
return NextResponse.json(companies);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const [companies, total] = await Promise.all([
|
|
82
|
+
prisma.company.findMany({
|
|
83
|
+
where,
|
|
84
|
+
include: {
|
|
85
|
+
_count: { select: { contacts: true } },
|
|
86
|
+
assignedCommercial: { select: { id: true, name: true, email: true } },
|
|
87
|
+
assignedTelepro: { select: { id: true, name: true, email: true } },
|
|
88
|
+
createdBy: { select: { id: true, name: true, email: true } },
|
|
89
|
+
},
|
|
90
|
+
orderBy: { createdAt: 'desc' },
|
|
91
|
+
skip,
|
|
92
|
+
take: limit,
|
|
93
|
+
}),
|
|
94
|
+
prisma.company.count({ where }),
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
return NextResponse.json({
|
|
98
|
+
companies,
|
|
99
|
+
pagination: {
|
|
100
|
+
total,
|
|
101
|
+
page,
|
|
102
|
+
limit,
|
|
103
|
+
totalPages: Math.ceil(total / limit),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('Erreur lors de la récupération des entreprises:', error);
|
|
108
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function POST(request: NextRequest) {
|
|
113
|
+
try {
|
|
114
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
115
|
+
if (!session) {
|
|
116
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const canCreate =
|
|
120
|
+
(await checkPermission('companies.create')) || (await checkPermission('contacts.create'));
|
|
121
|
+
if (!canCreate) {
|
|
122
|
+
return NextResponse.json({ error: 'Permission insuffisante' }, { status: 403 });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const json = await request.json();
|
|
126
|
+
const parseResult = createCompanySchema.safeParse(json);
|
|
127
|
+
|
|
128
|
+
if (!parseResult.success) {
|
|
129
|
+
return NextResponse.json(
|
|
130
|
+
{ error: 'Données invalides', details: parseResult.error.flatten() },
|
|
131
|
+
{ status: 400 },
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const data = parseResult.data;
|
|
136
|
+
|
|
137
|
+
const company = await prisma.company.create({
|
|
138
|
+
data: {
|
|
139
|
+
name: data.name,
|
|
140
|
+
phone: data.phone || null,
|
|
141
|
+
email: data.email || null,
|
|
142
|
+
address: data.address || null,
|
|
143
|
+
city: data.city || null,
|
|
144
|
+
postalCode: data.postalCode || null,
|
|
145
|
+
website: data.website || null,
|
|
146
|
+
siret: data.siret || null,
|
|
147
|
+
industry: data.industry || null,
|
|
148
|
+
notes: data.notes || null,
|
|
149
|
+
assignedCommercialId: data.assignedCommercialId || null,
|
|
150
|
+
assignedTeleproId: data.assignedTeleproId || null,
|
|
151
|
+
createdById: session.user.id,
|
|
152
|
+
},
|
|
153
|
+
include: {
|
|
154
|
+
_count: { select: { contacts: true } },
|
|
155
|
+
assignedCommercial: { select: { id: true, name: true, email: true } },
|
|
156
|
+
assignedTelepro: { select: { id: true, name: true, email: true } },
|
|
157
|
+
createdBy: { select: { id: true, name: true, email: true } },
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return NextResponse.json(company, { status: 201 });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("Erreur lors de la création de l'entreprise:", error);
|
|
164
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
|
|
5
|
+
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
try {
|
|
7
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
8
|
+
if (!session) {
|
|
9
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { id: viewId } = await params;
|
|
13
|
+
|
|
14
|
+
const view = await prisma.contactView.findUnique({ where: { id: viewId } });
|
|
15
|
+
if (!view) {
|
|
16
|
+
return NextResponse.json({ error: 'Vue non trouvée' }, { status: 404 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (view.userId !== session.user.id && !view.isPublic) {
|
|
20
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const maxPin = await prisma.contactViewPin.aggregate({
|
|
24
|
+
where: { userId: session.user.id },
|
|
25
|
+
_max: { pinOrder: true },
|
|
26
|
+
});
|
|
27
|
+
const nextOrder = (maxPin._max.pinOrder ?? -1) + 1;
|
|
28
|
+
|
|
29
|
+
const pin = await prisma.contactViewPin.upsert({
|
|
30
|
+
where: {
|
|
31
|
+
userId_viewId: { userId: session.user.id, viewId },
|
|
32
|
+
},
|
|
33
|
+
update: { pinOrder: nextOrder },
|
|
34
|
+
create: {
|
|
35
|
+
userId: session.user.id,
|
|
36
|
+
viewId,
|
|
37
|
+
pinOrder: nextOrder,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return NextResponse.json(pin, { status: 201 });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error("Erreur lors de l'épinglage:", error);
|
|
44
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function DELETE(
|
|
49
|
+
request: NextRequest,
|
|
50
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
51
|
+
) {
|
|
52
|
+
try {
|
|
53
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
54
|
+
if (!session) {
|
|
55
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { id: viewId } = await params;
|
|
59
|
+
|
|
60
|
+
await prisma.contactViewPin.deleteMany({
|
|
61
|
+
where: { userId: session.user.id, viewId },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return NextResponse.json({ success: true });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Erreur lors du désépinglage:', error);
|
|
67
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { auth } from '@/lib/auth';
|
|
4
|
+
import { prisma } from '@/lib/prisma';
|
|
5
|
+
|
|
6
|
+
async function getUserPermissions(userId: string): Promise<string[]> {
|
|
7
|
+
const user = await prisma.user.findUnique({
|
|
8
|
+
where: { id: userId },
|
|
9
|
+
include: { customRole: true },
|
|
10
|
+
});
|
|
11
|
+
return (user?.customRole?.permissions as string[]) ?? [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const updateViewSchema = z.object({
|
|
15
|
+
name: z.string().trim().min(1).optional(),
|
|
16
|
+
isPublic: z.boolean().optional(),
|
|
17
|
+
filters: z.array(z.any()).optional(),
|
|
18
|
+
columns: z
|
|
19
|
+
.array(
|
|
20
|
+
z.object({
|
|
21
|
+
id: z.string(),
|
|
22
|
+
visible: z.boolean(),
|
|
23
|
+
order: z.number(),
|
|
24
|
+
}),
|
|
25
|
+
)
|
|
26
|
+
.optional()
|
|
27
|
+
.nullable(),
|
|
28
|
+
sortConfig: z
|
|
29
|
+
.object({
|
|
30
|
+
field: z.string(),
|
|
31
|
+
direction: z.enum(['asc', 'desc']),
|
|
32
|
+
})
|
|
33
|
+
.optional()
|
|
34
|
+
.nullable(),
|
|
35
|
+
isDefault: z.boolean().optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
39
|
+
try {
|
|
40
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
41
|
+
if (!session) {
|
|
42
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { id } = await params;
|
|
46
|
+
|
|
47
|
+
const view = await prisma.contactView.findUnique({
|
|
48
|
+
where: { id },
|
|
49
|
+
include: { user: { select: { id: true, name: true } } },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!view) {
|
|
53
|
+
return NextResponse.json({ error: 'Vue non trouvée' }, { status: 404 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (view.userId !== session.user.id && !view.isPublic) {
|
|
57
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const pin = await prisma.contactViewPin.findUnique({
|
|
61
|
+
where: { userId_viewId: { userId: session.user.id, viewId: id } },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return NextResponse.json({ ...view, pinOrder: pin?.pinOrder ?? null });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Erreur lors de la récupération de la vue:', error);
|
|
67
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
72
|
+
try {
|
|
73
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
74
|
+
if (!session) {
|
|
75
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { id } = await params;
|
|
79
|
+
|
|
80
|
+
const existing = await prisma.contactView.findUnique({
|
|
81
|
+
where: { id },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!existing) {
|
|
85
|
+
return NextResponse.json({ error: 'Vue non trouvée' }, { status: 404 });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const isOwner = existing.userId === session.user.id;
|
|
89
|
+
const permissions = await getUserPermissions(session.user.id);
|
|
90
|
+
|
|
91
|
+
const canEdit = isOwner
|
|
92
|
+
? permissions.includes('contacts.views.edit_own')
|
|
93
|
+
: permissions.includes('contacts.views.edit_all');
|
|
94
|
+
|
|
95
|
+
if (!canEdit) {
|
|
96
|
+
return NextResponse.json(
|
|
97
|
+
{ error: 'Permission insuffisante pour modifier cette vue' },
|
|
98
|
+
{ status: 403 },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const json = await request.json();
|
|
103
|
+
const parseResult = updateViewSchema.safeParse(json);
|
|
104
|
+
|
|
105
|
+
if (!parseResult.success) {
|
|
106
|
+
return NextResponse.json(
|
|
107
|
+
{ error: 'Données invalides', details: parseResult.error.flatten() },
|
|
108
|
+
{ status: 400 },
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const data = parseResult.data;
|
|
113
|
+
|
|
114
|
+
if (data.isPublic !== undefined && !permissions.includes('contacts.views.share')) {
|
|
115
|
+
return NextResponse.json(
|
|
116
|
+
{ error: 'Permission insuffisante pour modifier la visibilité' },
|
|
117
|
+
{ status: 403 },
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (data.isDefault) {
|
|
122
|
+
await prisma.contactView.updateMany({
|
|
123
|
+
where: { userId: session.user.id, isDefault: true },
|
|
124
|
+
data: { isDefault: false },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const view = await prisma.contactView.update({
|
|
129
|
+
where: { id },
|
|
130
|
+
data: {
|
|
131
|
+
...(data.name !== undefined && { name: data.name }),
|
|
132
|
+
...(data.isPublic !== undefined && { isPublic: data.isPublic }),
|
|
133
|
+
...(data.filters !== undefined && { filters: data.filters as any }),
|
|
134
|
+
...(data.columns !== undefined && { columns: data.columns as any }),
|
|
135
|
+
...(data.sortConfig !== undefined && {
|
|
136
|
+
sortConfig: data.sortConfig as any,
|
|
137
|
+
}),
|
|
138
|
+
...(data.isDefault !== undefined && { isDefault: data.isDefault }),
|
|
139
|
+
},
|
|
140
|
+
include: {
|
|
141
|
+
user: { select: { id: true, name: true } },
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const pin = await prisma.contactViewPin.findUnique({
|
|
146
|
+
where: { userId_viewId: { userId: session.user.id, viewId: id } },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return NextResponse.json({ ...view, pinOrder: pin?.pinOrder ?? null });
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Erreur lors de la modification de la vue:', error);
|
|
152
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function DELETE(
|
|
157
|
+
request: NextRequest,
|
|
158
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
159
|
+
) {
|
|
160
|
+
try {
|
|
161
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
162
|
+
if (!session) {
|
|
163
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const { id } = await params;
|
|
167
|
+
|
|
168
|
+
const existing = await prisma.contactView.findUnique({
|
|
169
|
+
where: { id },
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!existing) {
|
|
173
|
+
return NextResponse.json({ error: 'Vue non trouvée' }, { status: 404 });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const isOwner = existing.userId === session.user.id;
|
|
177
|
+
const permissions = await getUserPermissions(session.user.id);
|
|
178
|
+
|
|
179
|
+
const canDelete = isOwner
|
|
180
|
+
? permissions.includes('contacts.views.delete_own')
|
|
181
|
+
: permissions.includes('contacts.views.delete_all');
|
|
182
|
+
|
|
183
|
+
if (!canDelete) {
|
|
184
|
+
return NextResponse.json(
|
|
185
|
+
{ error: 'Permission insuffisante pour supprimer cette vue' },
|
|
186
|
+
{ status: 403 },
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
await prisma.contactView.delete({ where: { id } });
|
|
191
|
+
|
|
192
|
+
return NextResponse.json({ success: true });
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Erreur lors de la suppression de la vue:', error);
|
|
195
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { auth } from '@/lib/auth';
|
|
4
|
+
import { prisma } from '@/lib/prisma';
|
|
5
|
+
|
|
6
|
+
async function getUserPermissions(userId: string): Promise<string[]> {
|
|
7
|
+
const user = await prisma.user.findUnique({
|
|
8
|
+
where: { id: userId },
|
|
9
|
+
include: { customRole: true },
|
|
10
|
+
});
|
|
11
|
+
return (user?.customRole?.permissions as string[]) ?? [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const createViewSchema = z.object({
|
|
15
|
+
name: z.string().trim().min(1, 'Le nom est obligatoire'),
|
|
16
|
+
isPublic: z.boolean().optional().default(false),
|
|
17
|
+
entityType: z.enum(['contacts', 'companies']).optional().default('contacts'),
|
|
18
|
+
filters: z.array(z.any()).optional().default([]),
|
|
19
|
+
columns: z
|
|
20
|
+
.array(
|
|
21
|
+
z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
visible: z.boolean(),
|
|
24
|
+
order: z.number(),
|
|
25
|
+
}),
|
|
26
|
+
)
|
|
27
|
+
.optional()
|
|
28
|
+
.nullable(),
|
|
29
|
+
sortConfig: z
|
|
30
|
+
.object({
|
|
31
|
+
field: z.string(),
|
|
32
|
+
direction: z.enum(['asc', 'desc']),
|
|
33
|
+
})
|
|
34
|
+
.optional()
|
|
35
|
+
.nullable(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export async function GET(request: NextRequest) {
|
|
39
|
+
try {
|
|
40
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
41
|
+
if (!session) {
|
|
42
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const userId = session.user.id;
|
|
46
|
+
const { searchParams } = new URL(request.url);
|
|
47
|
+
const entityType = searchParams.get('entityType') || 'contacts';
|
|
48
|
+
|
|
49
|
+
const [views, pins] = await Promise.all([
|
|
50
|
+
prisma.contactView.findMany({
|
|
51
|
+
where: {
|
|
52
|
+
entityType,
|
|
53
|
+
OR: [{ userId }, { isPublic: true }],
|
|
54
|
+
},
|
|
55
|
+
include: {
|
|
56
|
+
user: { select: { id: true, name: true } },
|
|
57
|
+
},
|
|
58
|
+
orderBy: { createdAt: 'asc' },
|
|
59
|
+
}),
|
|
60
|
+
prisma.contactViewPin.findMany({
|
|
61
|
+
where: { userId },
|
|
62
|
+
orderBy: { pinOrder: 'asc' },
|
|
63
|
+
}),
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const pinMap = new Map(pins.map((p) => [p.viewId, p.pinOrder]));
|
|
67
|
+
|
|
68
|
+
const viewsWithPins = views.map((v) => ({
|
|
69
|
+
...v,
|
|
70
|
+
pinOrder: pinMap.get(v.id) ?? null,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
viewsWithPins.sort((a, b) => {
|
|
74
|
+
const aPinned = a.pinOrder != null;
|
|
75
|
+
const bPinned = b.pinOrder != null;
|
|
76
|
+
if (aPinned && !bPinned) return -1;
|
|
77
|
+
if (!aPinned && bPinned) return 1;
|
|
78
|
+
if (aPinned && bPinned) return a.pinOrder! - b.pinOrder!;
|
|
79
|
+
// Vues non épinglées : ordre stable par date de création puis par nom
|
|
80
|
+
const createdCmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
81
|
+
if (createdCmp !== 0) return createdCmp;
|
|
82
|
+
return (a.name || '').localeCompare(b.name || '', 'fr');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return NextResponse.json(viewsWithPins);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Erreur lors de la récupération des vues:', error);
|
|
88
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function POST(request: NextRequest) {
|
|
93
|
+
try {
|
|
94
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
95
|
+
if (!session) {
|
|
96
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const permissions = await getUserPermissions(session.user.id);
|
|
100
|
+
if (!permissions.includes('contacts.views.create')) {
|
|
101
|
+
return NextResponse.json(
|
|
102
|
+
{ error: 'Permission insuffisante pour créer une vue' },
|
|
103
|
+
{ status: 403 },
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const json = await request.json();
|
|
108
|
+
const parseResult = createViewSchema.safeParse(json);
|
|
109
|
+
|
|
110
|
+
if (!parseResult.success) {
|
|
111
|
+
return NextResponse.json(
|
|
112
|
+
{ error: 'Données invalides', details: parseResult.error.flatten() },
|
|
113
|
+
{ status: 400 },
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { name, isPublic, entityType, filters, columns, sortConfig } = parseResult.data;
|
|
118
|
+
|
|
119
|
+
if (isPublic && !permissions.includes('contacts.views.share')) {
|
|
120
|
+
return NextResponse.json(
|
|
121
|
+
{ error: 'Permission insuffisante pour partager une vue' },
|
|
122
|
+
{ status: 403 },
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const view = await prisma.contactView.create({
|
|
127
|
+
data: {
|
|
128
|
+
name,
|
|
129
|
+
userId: session.user.id,
|
|
130
|
+
isPublic,
|
|
131
|
+
entityType,
|
|
132
|
+
filters: filters as any,
|
|
133
|
+
columns: columns as any,
|
|
134
|
+
sortConfig: sortConfig as any,
|
|
135
|
+
},
|
|
136
|
+
include: {
|
|
137
|
+
user: { select: { id: true, name: true } },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return NextResponse.json({ ...view, pinOrder: null }, { status: 201 });
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('Erreur lors de la création de la vue:', error);
|
|
144
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
5
|
+
import { downloadFileFromDrive } from '@/lib/google-drive';
|
|
6
|
+
|
|
7
|
+
// GET /api/contacts/[id]/files/[fileId]/preview - Prévisualiser une image de fichier contact
|
|
8
|
+
export async function GET(
|
|
9
|
+
request: NextRequest,
|
|
10
|
+
{ params }: { params: Promise<{ id: string; fileId: string }> },
|
|
11
|
+
) {
|
|
12
|
+
try {
|
|
13
|
+
const session = await auth.api.getSession({
|
|
14
|
+
headers: request.headers,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (!session) {
|
|
18
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const canViewFiles = await checkPermission('contacts.view_files');
|
|
22
|
+
if (!canViewFiles) {
|
|
23
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { id: contactId, fileId } = await params;
|
|
27
|
+
|
|
28
|
+
const contact = await prisma.contact.findUnique({
|
|
29
|
+
where: { id: contactId },
|
|
30
|
+
select: { id: true },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!contact) {
|
|
34
|
+
return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const file = await prisma.contactFile.findUnique({
|
|
38
|
+
where: { id: fileId },
|
|
39
|
+
select: {
|
|
40
|
+
id: true,
|
|
41
|
+
contactId: true,
|
|
42
|
+
fileName: true,
|
|
43
|
+
mimeType: true,
|
|
44
|
+
googleDriveFileId: true,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!file) {
|
|
49
|
+
return NextResponse.json({ error: 'Fichier non trouvé' }, { status: 404 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (file.contactId !== contactId) {
|
|
53
|
+
return NextResponse.json({ error: 'Fichier non associé à ce contact' }, { status: 403 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!file.mimeType.startsWith('image/')) {
|
|
57
|
+
return NextResponse.json({ error: 'Prévisualisation disponible uniquement pour les images' }, { status: 404 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const downloaded = await downloadFileFromDrive(session.user.id, file.googleDriveFileId);
|
|
61
|
+
|
|
62
|
+
return new NextResponse(new Uint8Array(downloaded.buffer), {
|
|
63
|
+
status: 200,
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': file.mimeType,
|
|
66
|
+
'Cache-Control': 'private, max-age=300',
|
|
67
|
+
'Content-Disposition': `inline; filename="${encodeURIComponent(file.fileName)}"`,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
} catch (error: any) {
|
|
71
|
+
console.error("Erreur lors de la prévisualisation d'image:", error);
|
|
72
|
+
return NextResponse.json(
|
|
73
|
+
{ error: error.message || "Erreur lors de la prévisualisation de l'image" },
|
|
74
|
+
{ status: 500 },
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|