create-crm-tmp 1.1.3 → 2.1.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/bin/create-crm-tmp.js +56 -35
- package/package.json +1 -1
- package/template/.prettierignore +2 -0
- package/template/README.md +230 -115
- package/template/components.json +22 -0
- package/template/eslint.config.mjs +13 -0
- package/template/exemple-contacts.csv +54 -0
- package/template/next.config.ts +41 -1
- package/template/package.json +63 -15
- package/template/prisma/migrations/20260318095700_init_db/migration.sql +978 -0
- package/template/prisma/schema.prisma +311 -67
- package/template/src/app/(auth)/invite/[token]/page.tsx +28 -29
- package/template/src/app/(auth)/layout.tsx +1 -1
- package/template/src/app/(auth)/reset-password/complete/page.tsx +21 -27
- package/template/src/app/(auth)/reset-password/page.tsx +14 -10
- package/template/src/app/(auth)/reset-password/verify/page.tsx +14 -10
- package/template/src/app/(auth)/signin/page.tsx +34 -23
- package/template/src/app/(dashboard)/agenda/page.tsx +3655 -2357
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +609 -338
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
- package/template/src/app/(dashboard)/automatisation/page.tsx +463 -186
- package/template/src/app/(dashboard)/closing/page.tsx +517 -469
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6151 -4210
- package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1702 -0
- package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +4124 -2130
- package/template/src/app/(dashboard)/dashboard/page.tsx +119 -105
- package/template/src/app/(dashboard)/dev/page.tsx +1291 -0
- package/template/src/app/(dashboard)/error.tsx +37 -0
- package/template/src/app/(dashboard)/layout.tsx +6 -2
- 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 +1773 -3362
- package/template/src/app/(dashboard)/templates/page.tsx +504 -303
- package/template/src/app/(dashboard)/users/list/page.tsx +364 -355
- 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 +169 -140
- package/template/src/app/api/agenda/google-events/route.ts +92 -0
- package/template/src/app/api/audit-logs/route.ts +1 -1
- package/template/src/app/api/auth/check-active/route.ts +3 -2
- 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/auth/google/route.ts +2 -1
- package/template/src/app/api/auth/google/status/route.ts +7 -31
- package/template/src/app/api/companies/[id]/activities/route.ts +129 -0
- package/template/src/app/api/companies/[id]/route.ts +194 -0
- package/template/src/app/api/companies/export/route.ts +206 -0
- package/template/src/app/api/companies/route.ts +196 -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 +55 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +20 -48
- package/template/src/app/api/contacts/[id]/files/route.ts +125 -186
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +27 -1
- package/template/src/app/api/contacts/[id]/interactions/route.ts +45 -8
- package/template/src/app/api/contacts/[id]/kyc/route.ts +81 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +55 -29
- package/template/src/app/api/contacts/[id]/route.ts +184 -21
- package/template/src/app/api/contacts/[id]/send-email/route.ts +33 -11
- package/template/src/app/api/contacts/[id]/workflows/run/route.ts +67 -0
- package/template/src/app/api/contacts/export/route.ts +22 -31
- package/template/src/app/api/contacts/import/route.ts +77 -44
- package/template/src/app/api/contacts/import-preview/route.ts +139 -0
- package/template/src/app/api/contacts/origins/route.ts +63 -0
- package/template/src/app/api/contacts/route.ts +322 -57
- package/template/src/app/api/cron/cleanup-editor-images/route.ts +166 -0
- package/template/src/app/api/dashboard/stats/route.ts +9 -292
- package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -3
- package/template/src/app/api/dashboard/widgets/route.ts +19 -19
- package/template/src/app/api/dev/reminders/test/route.ts +114 -0
- package/template/src/app/api/editor/upload-image/route.ts +61 -0
- package/template/src/app/api/integrations/google-sheet/jobs/[jobId]/route.ts +47 -0
- package/template/src/app/api/integrations/google-sheet/jobs/usage/route.ts +50 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +28 -542
- package/template/src/app/api/invite/complete/route.ts +20 -23
- package/template/src/app/api/jobs/google-sheet/process/route.ts +84 -0
- package/template/src/app/api/jobs/google-sheet/schedule/route.ts +50 -0
- package/template/src/app/api/reminders/clear/route.ts +120 -0
- package/template/src/app/api/reminders/clear/undo/route.ts +112 -0
- package/template/src/app/api/reminders/route.ts +165 -39
- package/template/src/app/api/reminders/state/route.ts +164 -0
- package/template/src/app/api/reset-password/complete/route.ts +11 -13
- package/template/src/app/api/reset-password/request/route.ts +1 -1
- package/template/src/app/api/reset-password/verify/route.ts +1 -1
- package/template/src/app/api/send/route.ts +25 -47
- 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 +34 -23
- package/template/src/app/api/settings/google-calendar/calendars/route.ts +97 -0
- package/template/src/app/api/settings/google-calendar/route.ts +124 -0
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +48 -23
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +56 -32
- package/template/src/app/api/settings/google-sheet/preview/route.ts +110 -0
- package/template/src/app/api/settings/google-sheet/route.ts +34 -23
- package/template/src/app/api/settings/integrations/logs/route.ts +93 -0
- package/template/src/app/api/settings/integrations/notifications/route.ts +67 -0
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -24
- package/template/src/app/api/settings/meta-leads/route.ts +34 -25
- package/template/src/app/api/settings/smtp/route.ts +53 -6
- package/template/src/app/api/settings/statuses/[id]/route.ts +29 -32
- 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 +36 -13
- package/template/src/app/api/tasks/[id]/route.ts +357 -145
- package/template/src/app/api/tasks/meet/route.ts +37 -26
- package/template/src/app/api/tasks/route.ts +201 -96
- 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 +22 -16
- 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/list/route.ts +57 -19
- package/template/src/app/api/users/route.ts +89 -34
- package/template/src/app/api/webhooks/google-ads/route.ts +40 -1
- package/template/src/app/api/webhooks/meta-leads/route.ts +38 -1
- package/template/src/app/api/workflows/[id]/route.ts +29 -6
- package/template/src/app/api/workflows/process/route.ts +505 -170
- package/template/src/app/api/workflows/route.ts +42 -4
- package/template/src/app/globals.css +512 -32
- package/template/src/app/layout.tsx +28 -9
- package/template/src/app/page.tsx +37 -7
- package/template/src/components/address-autocomplete.tsx +233 -0
- package/template/src/components/config-error-alert.tsx +46 -0
- package/template/src/components/contacts/filter-bar.tsx +190 -0
- package/template/src/components/contacts/filter-builder.tsx +574 -0
- package/template/src/components/contacts/save-view-dialog.tsx +160 -0
- package/template/src/components/contacts/views-tab-bar.tsx +449 -0
- package/template/src/components/dashboard/activity-chart.tsx +6 -1
- package/template/src/components/dashboard/add-widget-dialog.tsx +13 -17
- package/template/src/components/dashboard/color-picker.tsx +7 -8
- package/template/src/components/dashboard/recent-activity.tsx +2 -5
- package/template/src/components/dashboard/stat-card.tsx +1 -3
- package/template/src/components/dashboard/status-distribution-chart.tsx +0 -1
- package/template/src/components/dashboard/top-contacts-list.tsx +7 -13
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +2 -5
- package/template/src/components/dashboard/widget-wrapper.tsx +3 -6
- package/template/src/components/date-picker.tsx +399 -0
- package/template/src/components/editor/upload-editor-image.ts +42 -0
- package/template/src/components/editor.tsx +188 -35
- package/template/src/components/email-template.tsx +4 -2
- package/template/src/components/global-search.tsx +360 -0
- package/template/src/components/header.tsx +200 -107
- package/template/src/components/inactive-account-guard.tsx +58 -0
- package/template/src/components/integration-notifications-listener.tsx +12 -0
- 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/settings/integrations/GoogleAdsIntegration.tsx +428 -0
- package/template/src/components/settings/integrations/GoogleSheetConfigMonitoringModal.tsx +680 -0
- package/template/src/components/settings/integrations/GoogleSheetIntegration.tsx +809 -0
- package/template/src/components/settings/integrations/ImportResultDialog.tsx +124 -0
- package/template/src/components/settings/integrations/IntegrationLogPanel.tsx +57 -0
- package/template/src/components/settings/integrations/IntegrationLogsTable.tsx +186 -0
- package/template/src/components/settings/integrations/MetaLeadIntegration.tsx +451 -0
- package/template/src/components/sidebar.tsx +117 -100
- package/template/src/components/skeleton.tsx +128 -45
- 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 +71 -0
- package/template/src/components/ui/components.tsx +1 -1
- package/template/src/components/ui/date-picker.tsx +422 -0
- package/template/src/components/ui/datetime-picker.tsx +338 -0
- package/template/src/components/ui/status-select.tsx +271 -0
- package/template/src/components/ui/tooltip.tsx +37 -0
- package/template/src/components/view-as-banner.tsx +1 -1
- package/template/src/components/view-as-modal.tsx +30 -19
- package/template/src/config/nav-pages.ts +108 -0
- package/template/src/contexts/app-toast-context.tsx +362 -0
- package/template/src/contexts/dashboard-theme-context.tsx +2 -7
- package/template/src/contexts/sidebar-context.tsx +27 -53
- package/template/src/contexts/task-reminder-context.tsx +134 -160
- package/template/src/contexts/view-as-context.tsx +32 -10
- 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/hooks/useIntegrationNotifications.ts +49 -0
- package/template/src/lib/address-api.ts +155 -0
- package/template/src/lib/auth.ts +8 -1
- package/template/src/lib/cache.ts +73 -0
- package/template/src/lib/check-permission.ts +12 -177
- package/template/src/lib/config-links.ts +14 -0
- package/template/src/lib/contact-duplicate.ts +79 -61
- package/template/src/lib/contact-interactions.ts +24 -22
- package/template/src/lib/contact-view-filters.ts +301 -0
- package/template/src/lib/contacts-list-url.ts +190 -0
- package/template/src/lib/dashboard-stats.ts +282 -0
- package/template/src/lib/dashboard-themes.ts +0 -5
- package/template/src/lib/date-utils.ts +176 -0
- package/template/src/lib/default-widgets.ts +0 -2
- package/template/src/lib/editor-html-image-dimensions.ts +172 -0
- package/template/src/lib/editor-image-limits.ts +19 -0
- package/template/src/lib/email-html-sanitize.ts +19 -0
- package/template/src/lib/encryption.ts +9 -6
- package/template/src/lib/fr-geography.ts +192 -0
- package/template/src/lib/get-auth-user.ts +25 -0
- package/template/src/lib/google-calendar-agenda.ts +201 -0
- package/template/src/lib/google-calendar.ts +309 -17
- package/template/src/lib/google-fetch.ts +63 -0
- package/template/src/lib/google-sheet-sync-jobs.ts +96 -0
- package/template/src/lib/google-sheet-sync-runner.ts +514 -0
- package/template/src/lib/integration-import-log.ts +21 -0
- package/template/src/lib/local-storage.ts +34 -0
- package/template/src/lib/permissions.ts +268 -40
- package/template/src/lib/prisma.ts +15 -12
- package/template/src/lib/qstash.ts +65 -0
- package/template/src/lib/reminder-state-server.ts +80 -0
- package/template/src/lib/reminder-state.ts +29 -0
- package/template/src/lib/roles.ts +12 -15
- package/template/src/lib/supabase-storage.ts +113 -0
- package/template/src/lib/template-variables.ts +204 -29
- package/template/src/lib/utils.ts +71 -11
- package/template/src/lib/widget-registry.ts +0 -4
- package/template/src/lib/workflow-executor.ts +391 -228
- package/template/src/proxy.ts +35 -73
- package/template/src/types/contact-views.ts +351 -0
- package/template/vercel.json +5 -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/20260226093949_fix_cascade_on_user_delete/migration.sql +0 -69
- package/template/src/app/(dashboard)/users/layout.tsx +0 -30
- package/template/src/lib/google-drive.ts +0 -380
|
@@ -1,304 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { prisma } from '@/lib/prisma';
|
|
5
|
-
import { checkPermission } from '@/lib/check-permission';
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getAuthUser } from '@/lib/get-auth-user';
|
|
3
|
+
import { getDashboardStats } from '@/lib/dashboard-stats';
|
|
6
4
|
|
|
7
|
-
export async function GET(
|
|
5
|
+
export async function GET() {
|
|
8
6
|
try {
|
|
9
|
-
const
|
|
10
|
-
headers: await headers(),
|
|
11
|
-
});
|
|
7
|
+
const authUser = await getAuthUser();
|
|
12
8
|
|
|
13
|
-
if (!
|
|
9
|
+
if (!authUser) {
|
|
14
10
|
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
15
11
|
}
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
const canView = await checkPermission('dashboard.view');
|
|
19
|
-
if (!canView) {
|
|
13
|
+
if (!authUser.permissions.includes('dashboard.view')) {
|
|
20
14
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
21
15
|
}
|
|
22
16
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Déterminer les contacts visibles selon le rôle
|
|
27
|
-
const contactFilter =
|
|
28
|
-
userRole === 'ADMIN'
|
|
29
|
-
? {}
|
|
30
|
-
: userRole === 'MANAGER'
|
|
31
|
-
? {}
|
|
32
|
-
: userRole === 'COMMERCIAL'
|
|
33
|
-
? { assignedCommercialId: userId }
|
|
34
|
-
: userRole === 'TELEPRO'
|
|
35
|
-
? { assignedTeleproId: userId }
|
|
36
|
-
: { createdById: userId };
|
|
37
|
-
|
|
38
|
-
// 1. Total des contacts
|
|
39
|
-
const totalContacts = await prisma.contact.count({
|
|
40
|
-
where: contactFilter,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// 2. Contacts créés ce mois
|
|
44
|
-
const startOfMonth = new Date();
|
|
45
|
-
startOfMonth.setDate(1);
|
|
46
|
-
startOfMonth.setHours(0, 0, 0, 0);
|
|
47
|
-
|
|
48
|
-
const contactsThisMonth = await prisma.contact.count({
|
|
49
|
-
where: {
|
|
50
|
-
...contactFilter,
|
|
51
|
-
createdAt: { gte: startOfMonth },
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const lastMonthStart = new Date(startOfMonth);
|
|
56
|
-
lastMonthStart.setMonth(lastMonthStart.getMonth() - 1);
|
|
57
|
-
|
|
58
|
-
const contactsLastMonth = await prisma.contact.count({
|
|
59
|
-
where: {
|
|
60
|
-
...contactFilter,
|
|
61
|
-
createdAt: {
|
|
62
|
-
gte: lastMonthStart,
|
|
63
|
-
lt: startOfMonth,
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const contactsGrowth =
|
|
69
|
-
contactsLastMonth > 0
|
|
70
|
-
? ((contactsThisMonth - contactsLastMonth) / contactsLastMonth) * 100
|
|
71
|
-
: 0;
|
|
72
|
-
|
|
73
|
-
// 3. Contacts par mois (12 derniers mois)
|
|
74
|
-
const monthsData = [];
|
|
75
|
-
for (let i = 11; i >= 0; i--) {
|
|
76
|
-
const monthStart = new Date();
|
|
77
|
-
monthStart.setMonth(monthStart.getMonth() - i);
|
|
78
|
-
monthStart.setDate(1);
|
|
79
|
-
monthStart.setHours(0, 0, 0, 0);
|
|
80
|
-
|
|
81
|
-
const monthEnd = new Date(monthStart);
|
|
82
|
-
monthEnd.setMonth(monthEnd.getMonth() + 1);
|
|
83
|
-
|
|
84
|
-
const count = await prisma.contact.count({
|
|
85
|
-
where: {
|
|
86
|
-
...contactFilter,
|
|
87
|
-
createdAt: {
|
|
88
|
-
gte: monthStart,
|
|
89
|
-
lt: monthEnd,
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
monthsData.push({
|
|
95
|
-
month: monthStart.toLocaleString('fr-FR', { month: 'short' }),
|
|
96
|
-
count,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 4. Répartition par statut (pour le radar chart)
|
|
101
|
-
const statusDistribution = await prisma.contact.groupBy({
|
|
102
|
-
by: ['statusId'],
|
|
103
|
-
where: contactFilter,
|
|
104
|
-
_count: true,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const statuses = await prisma.status.findMany();
|
|
108
|
-
const statusData = statuses.map((status) => ({
|
|
109
|
-
name: status.name,
|
|
110
|
-
value: statusDistribution.find((s) => s.statusId === status.id)?._count || 0,
|
|
111
|
-
}));
|
|
112
|
-
|
|
113
|
-
// 5. Tâches à venir (Top tasks)
|
|
114
|
-
const upcomingTasks = await prisma.task.findMany({
|
|
115
|
-
where: {
|
|
116
|
-
assignedUserId: userId,
|
|
117
|
-
completed: false,
|
|
118
|
-
scheduledAt: { gte: new Date() },
|
|
119
|
-
},
|
|
120
|
-
include: {
|
|
121
|
-
contact: true,
|
|
122
|
-
assignedUser: true,
|
|
123
|
-
},
|
|
124
|
-
orderBy: { scheduledAt: 'asc' },
|
|
125
|
-
take: 6,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// 6. Interactions récentes
|
|
129
|
-
const recentInteractions = await prisma.interaction.findMany({
|
|
130
|
-
where: {
|
|
131
|
-
userId,
|
|
132
|
-
},
|
|
133
|
-
include: {
|
|
134
|
-
contact: true,
|
|
135
|
-
user: true,
|
|
136
|
-
},
|
|
137
|
-
orderBy: { createdAt: 'desc' },
|
|
138
|
-
take: 5,
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// 7. Statistiques des tâches
|
|
142
|
-
const totalTasks = await prisma.task.count({
|
|
143
|
-
where: { assignedUserId: userId },
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const completedTasks = await prisma.task.count({
|
|
147
|
-
where: {
|
|
148
|
-
assignedUserId: userId,
|
|
149
|
-
completed: true,
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const pendingTasks = totalTasks - completedTasks;
|
|
154
|
-
|
|
155
|
-
// 8. Tâches par type ce mois
|
|
156
|
-
const tasksThisMonthByType = await prisma.task.groupBy({
|
|
157
|
-
by: ['type'],
|
|
158
|
-
where: {
|
|
159
|
-
assignedUserId: userId,
|
|
160
|
-
createdAt: { gte: startOfMonth },
|
|
161
|
-
},
|
|
162
|
-
_count: true,
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const tasksByType = tasksThisMonthByType.map((t) => ({
|
|
166
|
-
type: t.type,
|
|
167
|
-
count: t._count,
|
|
168
|
-
}));
|
|
169
|
-
|
|
170
|
-
// 9. Interactions par type ce mois
|
|
171
|
-
const interactionsThisMonth = await prisma.interaction.groupBy({
|
|
172
|
-
by: ['type'],
|
|
173
|
-
where: {
|
|
174
|
-
userId,
|
|
175
|
-
createdAt: { gte: startOfMonth },
|
|
176
|
-
},
|
|
177
|
-
_count: true,
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const interactionsByType = interactionsThisMonth.map((i) => ({
|
|
181
|
-
type: i.type,
|
|
182
|
-
count: i._count,
|
|
183
|
-
}));
|
|
184
|
-
|
|
185
|
-
// 10. Activité des 7 derniers jours
|
|
186
|
-
const last7Days = [];
|
|
187
|
-
for (let i = 6; i >= 0; i--) {
|
|
188
|
-
const dayStart = new Date();
|
|
189
|
-
dayStart.setDate(dayStart.getDate() - i);
|
|
190
|
-
dayStart.setHours(0, 0, 0, 0);
|
|
191
|
-
|
|
192
|
-
const dayEnd = new Date(dayStart);
|
|
193
|
-
dayEnd.setDate(dayEnd.getDate() + 1);
|
|
194
|
-
|
|
195
|
-
const interactionsCount = await prisma.interaction.count({
|
|
196
|
-
where: {
|
|
197
|
-
userId,
|
|
198
|
-
createdAt: {
|
|
199
|
-
gte: dayStart,
|
|
200
|
-
lt: dayEnd,
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
const tasksCount = await prisma.task.count({
|
|
206
|
-
where: {
|
|
207
|
-
assignedUserId: userId,
|
|
208
|
-
createdAt: {
|
|
209
|
-
gte: dayStart,
|
|
210
|
-
lt: dayEnd,
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
last7Days.push({
|
|
216
|
-
date: dayStart.toLocaleDateString('fr-FR', {
|
|
217
|
-
day: 'numeric',
|
|
218
|
-
month: 'short',
|
|
219
|
-
}),
|
|
220
|
-
interactions: interactionsCount,
|
|
221
|
-
tasks: tasksCount,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// 11. Top contacts (ceux avec le plus d'interactions)
|
|
226
|
-
const topContacts = await prisma.contact.findMany({
|
|
227
|
-
where: contactFilter,
|
|
228
|
-
include: {
|
|
229
|
-
_count: {
|
|
230
|
-
select: { interactions: true },
|
|
231
|
-
},
|
|
232
|
-
status: true,
|
|
233
|
-
assignedCommercial: true,
|
|
234
|
-
assignedTelepro: true,
|
|
235
|
-
},
|
|
236
|
-
orderBy: {
|
|
237
|
-
createdAt: 'desc',
|
|
238
|
-
},
|
|
239
|
-
take: 4,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
return NextResponse.json({
|
|
243
|
-
overview: {
|
|
244
|
-
totalContacts,
|
|
245
|
-
contactsThisMonth,
|
|
246
|
-
contactsGrowth: Math.round(contactsGrowth * 10) / 10,
|
|
247
|
-
monthsData,
|
|
248
|
-
},
|
|
249
|
-
statusDistribution: statusData,
|
|
250
|
-
tasks: {
|
|
251
|
-
total: totalTasks,
|
|
252
|
-
completed: completedTasks,
|
|
253
|
-
pending: pendingTasks,
|
|
254
|
-
upcoming: upcomingTasks.map((task) => ({
|
|
255
|
-
id: task.id,
|
|
256
|
-
title: task.title || 'Sans titre',
|
|
257
|
-
description: task.description,
|
|
258
|
-
type: task.type,
|
|
259
|
-
scheduledAt: task.scheduledAt,
|
|
260
|
-
contact: task.contact
|
|
261
|
-
? {
|
|
262
|
-
id: task.contact.id,
|
|
263
|
-
name:
|
|
264
|
-
`${task.contact.firstName || ''} ${task.contact.lastName || ''}`.trim() ||
|
|
265
|
-
task.contact.phone,
|
|
266
|
-
}
|
|
267
|
-
: null,
|
|
268
|
-
priority: task.priority,
|
|
269
|
-
})),
|
|
270
|
-
byType: tasksByType,
|
|
271
|
-
},
|
|
272
|
-
interactions: {
|
|
273
|
-
recent: recentInteractions.map((interaction) => ({
|
|
274
|
-
id: interaction.id,
|
|
275
|
-
type: interaction.type,
|
|
276
|
-
title: interaction.title,
|
|
277
|
-
content: interaction.content,
|
|
278
|
-
date: interaction.date || interaction.createdAt,
|
|
279
|
-
contact: {
|
|
280
|
-
id: interaction.contact.id,
|
|
281
|
-
name:
|
|
282
|
-
`${interaction.contact.firstName || ''} ${interaction.contact.lastName || ''}`.trim() ||
|
|
283
|
-
interaction.contact.phone,
|
|
284
|
-
},
|
|
285
|
-
})),
|
|
286
|
-
byType: interactionsByType,
|
|
287
|
-
},
|
|
288
|
-
activity: {
|
|
289
|
-
last7Days,
|
|
290
|
-
},
|
|
291
|
-
topContacts: topContacts.map((contact) => ({
|
|
292
|
-
id: contact.id,
|
|
293
|
-
name: `${contact.firstName || ''} ${contact.lastName || ''}`.trim() || contact.phone,
|
|
294
|
-
phone: contact.phone,
|
|
295
|
-
email: contact.email,
|
|
296
|
-
status: contact.status?.name || 'Non défini',
|
|
297
|
-
interactionsCount: contact._count.interactions,
|
|
298
|
-
assignedCommercial: contact.assignedCommercial?.name,
|
|
299
|
-
assignedTelepro: contact.assignedTelepro?.name,
|
|
300
|
-
})),
|
|
301
|
-
});
|
|
17
|
+
const stats = await getDashboardStats(authUser.session.user.id, authUser.permissions);
|
|
18
|
+
return NextResponse.json(stats);
|
|
302
19
|
} catch (error) {
|
|
303
20
|
console.error('Erreur lors de la récupération des statistiques:', error);
|
|
304
21
|
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
@@ -4,7 +4,6 @@ import { headers } from 'next/headers';
|
|
|
4
4
|
import { prisma } from '@/lib/prisma';
|
|
5
5
|
import { checkPermission } from '@/lib/check-permission';
|
|
6
6
|
|
|
7
|
-
// DELETE - Supprimer un widget
|
|
8
7
|
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
9
8
|
try {
|
|
10
9
|
const session = await auth.api.getSession({
|
|
@@ -15,7 +14,6 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
|
|
15
14
|
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
// Vérifier la permission de gestion des widgets
|
|
19
17
|
const canManage = await checkPermission('dashboard.widgets.manage');
|
|
20
18
|
if (!canManage) {
|
|
21
19
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
@@ -23,7 +21,6 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
|
|
23
21
|
|
|
24
22
|
const { id } = await params;
|
|
25
23
|
|
|
26
|
-
// Vérifier que le widget appartient à l'utilisateur
|
|
27
24
|
const widget = await prisma.dashboardWidget.findFirst({
|
|
28
25
|
where: {
|
|
29
26
|
id,
|
|
@@ -5,7 +5,6 @@ import { prisma } from '@/lib/prisma';
|
|
|
5
5
|
import { DEFAULT_WIDGETS } from '@/lib/default-widgets';
|
|
6
6
|
import { checkPermission } from '@/lib/check-permission';
|
|
7
7
|
|
|
8
|
-
// GET - Récupérer les widgets de l'utilisateur
|
|
9
8
|
export async function GET() {
|
|
10
9
|
try {
|
|
11
10
|
const session = await auth.api.getSession({
|
|
@@ -16,17 +15,33 @@ export async function GET() {
|
|
|
16
15
|
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
// Vérifier la permission d'accès au tableau de bord
|
|
20
18
|
const canView = await checkPermission('dashboard.view');
|
|
21
19
|
if (!canView) {
|
|
22
20
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
let widgets = await prisma.dashboardWidget.findMany({
|
|
26
24
|
where: { userId: session.user.id },
|
|
27
25
|
orderBy: [{ y: 'asc' }, { x: 'asc' }],
|
|
28
26
|
});
|
|
29
27
|
|
|
28
|
+
if (widgets.length === 0) {
|
|
29
|
+
widgets = await prisma.$transaction(
|
|
30
|
+
DEFAULT_WIDGETS.map((dw) =>
|
|
31
|
+
prisma.dashboardWidget.create({
|
|
32
|
+
data: {
|
|
33
|
+
userId: session.user.id,
|
|
34
|
+
type: dw.type,
|
|
35
|
+
x: dw.x,
|
|
36
|
+
y: dw.y,
|
|
37
|
+
w: dw.w,
|
|
38
|
+
h: dw.h,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
return NextResponse.json(widgets);
|
|
31
46
|
} catch (error) {
|
|
32
47
|
console.error('Erreur lors de la récupération des widgets:', error);
|
|
@@ -34,7 +49,6 @@ export async function GET() {
|
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
// POST - Créer un widget (ou initialiser le layout par défaut)
|
|
38
52
|
export async function POST(req: NextRequest) {
|
|
39
53
|
try {
|
|
40
54
|
const session = await auth.api.getSession({
|
|
@@ -45,7 +59,6 @@ export async function POST(req: NextRequest) {
|
|
|
45
59
|
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
46
60
|
}
|
|
47
61
|
|
|
48
|
-
// Vérifier la permission de gestion des widgets
|
|
49
62
|
const canManage = await checkPermission('dashboard.widgets.manage');
|
|
50
63
|
if (!canManage) {
|
|
51
64
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
@@ -53,20 +66,11 @@ export async function POST(req: NextRequest) {
|
|
|
53
66
|
|
|
54
67
|
const body = await req.json();
|
|
55
68
|
|
|
56
|
-
// Si c'est une initialisation par défaut
|
|
57
69
|
if (body.initDefault) {
|
|
58
|
-
|
|
70
|
+
await prisma.dashboardWidget.deleteMany({
|
|
59
71
|
where: { userId: session.user.id },
|
|
60
72
|
});
|
|
61
73
|
|
|
62
|
-
if (existing > 0) {
|
|
63
|
-
const widgets = await prisma.dashboardWidget.findMany({
|
|
64
|
-
where: { userId: session.user.id },
|
|
65
|
-
orderBy: [{ y: 'asc' }, { x: 'asc' }],
|
|
66
|
-
});
|
|
67
|
-
return NextResponse.json(widgets);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
74
|
const created = await prisma.$transaction(
|
|
71
75
|
DEFAULT_WIDGETS.map((dw) =>
|
|
72
76
|
prisma.dashboardWidget.create({
|
|
@@ -85,7 +89,6 @@ export async function POST(req: NextRequest) {
|
|
|
85
89
|
return NextResponse.json(created, { status: 201 });
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
// Création d'un widget unique
|
|
89
92
|
const {
|
|
90
93
|
type,
|
|
91
94
|
x,
|
|
@@ -104,7 +107,6 @@ export async function POST(req: NextRequest) {
|
|
|
104
107
|
return NextResponse.json({ error: 'Le type de widget est requis' }, { status: 400 });
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
// Calculer la position Y max pour placer le nouveau widget en bas
|
|
108
110
|
const lastWidget = await prisma.dashboardWidget.findFirst({
|
|
109
111
|
where: { userId: session.user.id },
|
|
110
112
|
orderBy: { y: 'desc' },
|
|
@@ -130,7 +132,6 @@ export async function POST(req: NextRequest) {
|
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
// PUT - Mettre à jour les positions des widgets (batch)
|
|
134
135
|
export async function PUT(req: NextRequest) {
|
|
135
136
|
try {
|
|
136
137
|
const session = await auth.api.getSession({
|
|
@@ -141,7 +142,6 @@ export async function PUT(req: NextRequest) {
|
|
|
141
142
|
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
// Vérifier la permission de gestion des widgets
|
|
145
145
|
const canManage = await checkPermission('dashboard.widgets.manage');
|
|
146
146
|
if (!canManage) {
|
|
147
147
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
|
|
5
|
+
type TestReminderBody = {
|
|
6
|
+
title?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
scheduledAt?: string;
|
|
9
|
+
reminderMinutesBefore?: number;
|
|
10
|
+
sendNow?: boolean;
|
|
11
|
+
contactId?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// WARNING: This endpoint is for local development only.
|
|
15
|
+
// It is protected by NODE_ENV and an optional DEV_API_SECRET.
|
|
16
|
+
// Never expose dev endpoints in production builds.
|
|
17
|
+
export async function POST(request: NextRequest) {
|
|
18
|
+
if (
|
|
19
|
+
process.env.NODE_ENV !== 'development' ||
|
|
20
|
+
(process.env.DEV_API_SECRET &&
|
|
21
|
+
process.env.DEV_API_SECRET !== request.headers.get('x-dev-secret'))
|
|
22
|
+
) {
|
|
23
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
28
|
+
if (!session) {
|
|
29
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const body = (await request.json().catch(() => ({}))) as TestReminderBody;
|
|
33
|
+
const title = body.title?.trim() || 'Rappel de test';
|
|
34
|
+
const description = body.description?.trim() || 'Rappel généré en mode développement.';
|
|
35
|
+
const reminderMinutesBefore =
|
|
36
|
+
typeof body.reminderMinutesBefore === 'number' &&
|
|
37
|
+
Number.isInteger(body.reminderMinutesBefore) &&
|
|
38
|
+
body.reminderMinutesBefore > 0
|
|
39
|
+
? body.reminderMinutesBefore
|
|
40
|
+
: 5;
|
|
41
|
+
|
|
42
|
+
const parsedDate = body.scheduledAt ? new Date(body.scheduledAt) : null;
|
|
43
|
+
const scheduledAt =
|
|
44
|
+
parsedDate && !Number.isNaN(parsedDate.getTime())
|
|
45
|
+
? parsedDate
|
|
46
|
+
: new Date(Date.now() + (reminderMinutesBefore + 1) * 60 * 1000);
|
|
47
|
+
|
|
48
|
+
const normalizedContactId =
|
|
49
|
+
typeof body.contactId === 'string' && body.contactId.trim() !== '' ? body.contactId.trim() : null;
|
|
50
|
+
let contact:
|
|
51
|
+
| {
|
|
52
|
+
id: string;
|
|
53
|
+
firstName: string | null;
|
|
54
|
+
lastName: string | null;
|
|
55
|
+
}
|
|
56
|
+
| null = null;
|
|
57
|
+
if (normalizedContactId) {
|
|
58
|
+
contact = await prisma.contact.findUnique({
|
|
59
|
+
where: { id: normalizedContactId },
|
|
60
|
+
select: { id: true, firstName: true, lastName: true },
|
|
61
|
+
});
|
|
62
|
+
if (!contact) {
|
|
63
|
+
return NextResponse.json({ error: 'Contact introuvable.' }, { status: 404 });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const task = await prisma.task.create({
|
|
68
|
+
data: {
|
|
69
|
+
type: 'OTHER',
|
|
70
|
+
title,
|
|
71
|
+
description,
|
|
72
|
+
priority: 'MEDIUM',
|
|
73
|
+
scheduledAt,
|
|
74
|
+
reminderMinutesBefore,
|
|
75
|
+
assignedUserId: session.user.id,
|
|
76
|
+
createdById: session.user.id,
|
|
77
|
+
contactId: contact?.id ?? null,
|
|
78
|
+
},
|
|
79
|
+
select: {
|
|
80
|
+
id: true,
|
|
81
|
+
title: true,
|
|
82
|
+
scheduledAt: true,
|
|
83
|
+
reminderMinutesBefore: true,
|
|
84
|
+
contact: {
|
|
85
|
+
select: {
|
|
86
|
+
id: true,
|
|
87
|
+
firstName: true,
|
|
88
|
+
lastName: true,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const sendNow = body.sendNow === true;
|
|
95
|
+
const immediateNotification = sendNow
|
|
96
|
+
? {
|
|
97
|
+
tone: 'warning' as const,
|
|
98
|
+
message: `Test immédiat: ${task.title || 'Rappel'} (${task.reminderMinutesBefore} min avant)`,
|
|
99
|
+
link: task.contact ? `/contacts/${task.contact.id}` : undefined,
|
|
100
|
+
actionLabel: task.contact ? 'Ouvrir le contact' : undefined,
|
|
101
|
+
}
|
|
102
|
+
: null;
|
|
103
|
+
|
|
104
|
+
return NextResponse.json({
|
|
105
|
+
success: true,
|
|
106
|
+
task,
|
|
107
|
+
immediateNotification,
|
|
108
|
+
});
|
|
109
|
+
} catch (error: any) {
|
|
110
|
+
console.error('Erreur lors de la création du rappel de test:', error);
|
|
111
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import {
|
|
4
|
+
BUCKETS,
|
|
5
|
+
buildEditorImagePath,
|
|
6
|
+
createSignedUploadUrl,
|
|
7
|
+
} from '@/lib/supabase-storage';
|
|
8
|
+
|
|
9
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
10
|
+
|
|
11
|
+
// POST /api/editor/upload-image - Créer une URL d'upload signée pour une image éditeur
|
|
12
|
+
export async function POST(request: NextRequest) {
|
|
13
|
+
try {
|
|
14
|
+
const session = await auth.api.getSession({
|
|
15
|
+
headers: request.headers,
|
|
16
|
+
});
|
|
17
|
+
if (!session) {
|
|
18
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const body = await request.json();
|
|
22
|
+
const { fileName, fileSize, mimeType } = body;
|
|
23
|
+
|
|
24
|
+
if (!fileName || typeof fileSize !== 'number' || !mimeType) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: 'Champs requis: fileName, fileSize (number), mimeType' },
|
|
27
|
+
{ status: 400 },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
|
|
32
|
+
if (!ALLOWED_IMAGE_TYPES.includes(mimeType)) {
|
|
33
|
+
return NextResponse.json(
|
|
34
|
+
{ error: 'Seuls les formats JPEG, PNG, WebP et GIF sont acceptés' },
|
|
35
|
+
{ status: 400 },
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (fileSize > MAX_FILE_SIZE) {
|
|
40
|
+
return NextResponse.json(
|
|
41
|
+
{ error: 'Image trop volumineuse. Taille maximale: 10MB' },
|
|
42
|
+
{ status: 400 },
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const storagePath = buildEditorImagePath(fileName);
|
|
47
|
+
const result = await createSignedUploadUrl(BUCKETS.EDITOR_IMAGES, storagePath);
|
|
48
|
+
|
|
49
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
50
|
+
const publicUrl = `${supabaseUrl}/storage/v1/object/public/${BUCKETS.EDITOR_IMAGES}/${storagePath}`;
|
|
51
|
+
|
|
52
|
+
return NextResponse.json({
|
|
53
|
+
...result,
|
|
54
|
+
publicUrl,
|
|
55
|
+
});
|
|
56
|
+
} catch (error: unknown) {
|
|
57
|
+
console.error("Erreur lors de la création de l'URL d'upload image:", error);
|
|
58
|
+
const message = error instanceof Error ? error.message : 'Erreur serveur';
|
|
59
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
4
|
+
import { prisma } from '@/lib/prisma';
|
|
5
|
+
|
|
6
|
+
export async function GET(
|
|
7
|
+
request: NextRequest,
|
|
8
|
+
{ params }: { params: Promise<{ jobId: string }> },
|
|
9
|
+
) {
|
|
10
|
+
try {
|
|
11
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
12
|
+
if (!session) {
|
|
13
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { jobId } = await params;
|
|
17
|
+
const job = await prisma.googleSheetSyncJob.findUnique({
|
|
18
|
+
where: { id: jobId },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!job) {
|
|
22
|
+
return NextResponse.json({ error: 'Job introuvable' }, { status: 404 });
|
|
23
|
+
}
|
|
24
|
+
const isOwner = job.requestedByUserId === session.user.id;
|
|
25
|
+
const isScheduledOrSystem = job.requestedByUserId === null;
|
|
26
|
+
const canManageIntegrations = await checkPermission('integrations.google_sheets.manage');
|
|
27
|
+
if (!isOwner && !isScheduledOrSystem && !canManageIntegrations) {
|
|
28
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return NextResponse.json({
|
|
32
|
+
id: job.id,
|
|
33
|
+
status: job.status,
|
|
34
|
+
triggerType: job.triggerType,
|
|
35
|
+
configId: job.configId,
|
|
36
|
+
startedAt: job.startedAt,
|
|
37
|
+
finishedAt: job.finishedAt,
|
|
38
|
+
error: job.error,
|
|
39
|
+
result: job.result,
|
|
40
|
+
createdAt: job.createdAt,
|
|
41
|
+
updatedAt: job.updatedAt,
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Erreur lecture job Google Sheet:', error);
|
|
45
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
46
|
+
}
|
|
47
|
+
}
|