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,32 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { auth } from '@/lib/auth';
|
|
3
4
|
import { prisma } from '@/lib/prisma';
|
|
5
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
4
6
|
import { handleContactDuplicate } from '@/lib/contact-duplicate';
|
|
5
7
|
import { executeWorkflowsOnContactCreated } from '@/lib/workflow-executor';
|
|
6
8
|
import { normalizePhoneNumber } from '@/lib/utils';
|
|
9
|
+
import { buildPrismaWhereFromFilters } from '@/lib/contact-view-filters';
|
|
10
|
+
import type { ViewFilter, ViewSortConfig } from '@/types/contact-views';
|
|
11
|
+
|
|
12
|
+
const createContactSchema = z.object({
|
|
13
|
+
civility: z.enum(['M', 'MME', 'MLLE']).optional().nullable(),
|
|
14
|
+
firstName: z.string().trim().min(1).optional().nullable(),
|
|
15
|
+
lastName: z.string().trim().min(1).optional().nullable(),
|
|
16
|
+
phone: z.string().trim().min(3, 'Le téléphone est obligatoire'),
|
|
17
|
+
secondaryPhone: z.string().trim().optional().nullable(),
|
|
18
|
+
email: z.string().email().optional().nullable(),
|
|
19
|
+
address: z.string().optional().nullable(),
|
|
20
|
+
city: z.string().optional().nullable(),
|
|
21
|
+
postalCode: z.string().optional().nullable(),
|
|
22
|
+
origin: z.string().optional().nullable(),
|
|
23
|
+
companyId: z.string().optional().nullable(),
|
|
24
|
+
jobTitle: z.string().trim().optional().nullable(),
|
|
25
|
+
statusId: z.string().optional().nullable(),
|
|
26
|
+
closingReason: z.string().optional().nullable(),
|
|
27
|
+
assignedCommercialId: z.string().optional().nullable(),
|
|
28
|
+
assignedTeleproId: z.string().optional().nullable(),
|
|
29
|
+
});
|
|
7
30
|
|
|
8
31
|
// GET /api/contacts - Récupérer tous les contacts avec filtres
|
|
9
32
|
export async function GET(request: NextRequest) {
|
|
@@ -16,8 +39,24 @@ export async function GET(request: NextRequest) {
|
|
|
16
39
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
17
40
|
}
|
|
18
41
|
|
|
42
|
+
const [canViewAll, canViewOwn, canViewUnassigned] = await Promise.all([
|
|
43
|
+
checkPermission('contacts.view_all'),
|
|
44
|
+
checkPermission('contacts.view_own'),
|
|
45
|
+
checkPermission('contacts.view_unassigned'),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
if (!canViewAll && !canViewOwn) {
|
|
49
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
50
|
+
}
|
|
51
|
+
|
|
19
52
|
const { searchParams } = new URL(request.url);
|
|
20
53
|
const search = searchParams.get('search') || '';
|
|
54
|
+
const viewId = searchParams.get('viewId');
|
|
55
|
+
const filtersParam = searchParams.get('filters');
|
|
56
|
+
const statusIds = searchParams.get('statusIds');
|
|
57
|
+
const assignedCommercialIds = searchParams.get('assignedCommercialIds');
|
|
58
|
+
const assignedTeleproIds = searchParams.get('assignedTeleproIds');
|
|
59
|
+
const origins = searchParams.get('origins');
|
|
21
60
|
const statusId = searchParams.get('statusId');
|
|
22
61
|
const assignedCommercialId = searchParams.get('assignedCommercialId');
|
|
23
62
|
const assignedTeleproId = searchParams.get('assignedTeleproId');
|
|
@@ -26,40 +65,122 @@ export async function GET(request: NextRequest) {
|
|
|
26
65
|
const createdAtEnd = searchParams.get('createdAtEnd');
|
|
27
66
|
const updatedAtStart = searchParams.get('updatedAtStart');
|
|
28
67
|
const updatedAtEnd = searchParams.get('updatedAtEnd');
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
68
|
+
const sortFieldParam = searchParams.get('sortField');
|
|
69
|
+
const sortDirParam = searchParams.get('sortDir');
|
|
70
|
+
const page = Number.parseInt(searchParams.get('page') || '1');
|
|
71
|
+
const limit = Number.parseInt(searchParams.get('limit') || '50');
|
|
32
72
|
const skip = (page - 1) * limit;
|
|
33
73
|
|
|
34
|
-
// Construire les filtres
|
|
35
74
|
const where: any = {};
|
|
75
|
+
let viewSortConfig: ViewSortConfig | null = null;
|
|
36
76
|
|
|
37
|
-
if
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
77
|
+
// Resolve view filters if viewId or inline filters are provided
|
|
78
|
+
if (viewId) {
|
|
79
|
+
const view = await prisma.contactView.findUnique({ where: { id: viewId } });
|
|
80
|
+
if (view && (view.userId === session.user.id || view.isPublic)) {
|
|
81
|
+
const viewFilters = (view.filters ?? []) as unknown as ViewFilter[];
|
|
82
|
+
const viewWhere = buildPrismaWhereFromFilters(viewFilters);
|
|
83
|
+
if (Object.keys(viewWhere).length > 0) {
|
|
84
|
+
where.AND = where.AND || [];
|
|
85
|
+
where.AND.push(viewWhere);
|
|
86
|
+
}
|
|
87
|
+
if (view.sortConfig) {
|
|
88
|
+
viewSortConfig = view.sortConfig as unknown as ViewSortConfig;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else if (filtersParam) {
|
|
92
|
+
try {
|
|
93
|
+
const parsedFilters = JSON.parse(filtersParam) as ViewFilter[];
|
|
94
|
+
if (Array.isArray(parsedFilters) && parsedFilters.length > 0) {
|
|
95
|
+
const filtersWhere = buildPrismaWhereFromFilters(parsedFilters);
|
|
96
|
+
if (Object.keys(filtersWhere).length > 0) {
|
|
97
|
+
where.AND = where.AND || [];
|
|
98
|
+
where.AND.push(filtersWhere);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Invalid JSON, ignore
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!canViewAll && canViewOwn) {
|
|
107
|
+
const ownershipConditions: any[] = [
|
|
108
|
+
{ assignedCommercialId: session.user.id },
|
|
109
|
+
{ assignedTeleproId: session.user.id },
|
|
110
|
+
{ createdById: session.user.id },
|
|
43
111
|
];
|
|
112
|
+
if (canViewUnassigned) {
|
|
113
|
+
ownershipConditions.push({
|
|
114
|
+
AND: [{ assignedCommercialId: null }, { assignedTeleproId: null }],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
where.AND = where.AND || [];
|
|
118
|
+
where.AND.push({ OR: ownershipConditions });
|
|
44
119
|
}
|
|
45
120
|
|
|
46
|
-
if (
|
|
121
|
+
if (search) {
|
|
122
|
+
where.AND = where.AND || [];
|
|
123
|
+
where.AND.push({
|
|
124
|
+
OR: [
|
|
125
|
+
{ firstName: { contains: search, mode: 'insensitive' } },
|
|
126
|
+
{ lastName: { contains: search, mode: 'insensitive' } },
|
|
127
|
+
{ email: { contains: search, mode: 'insensitive' } },
|
|
128
|
+
{ phone: { contains: search, mode: 'insensitive' } },
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Legacy query-param filters (rétrocompatibilité)
|
|
134
|
+
if (statusIds) {
|
|
135
|
+
const ids = statusIds.split(',').filter(Boolean);
|
|
136
|
+
where.statusId = ids.length === 1 ? ids[0] : { in: ids };
|
|
137
|
+
} else if (statusId) {
|
|
47
138
|
where.statusId = statusId;
|
|
48
139
|
}
|
|
49
140
|
|
|
50
|
-
if (
|
|
141
|
+
if (assignedCommercialIds) {
|
|
142
|
+
const ids = assignedCommercialIds.split(',').filter(Boolean);
|
|
143
|
+
const hasUnassigned = ids.includes('UNASSIGNED');
|
|
144
|
+
const realIds = ids.filter((id) => id !== 'UNASSIGNED');
|
|
145
|
+
if (hasUnassigned && realIds.length > 0) {
|
|
146
|
+
where.AND = where.AND || [];
|
|
147
|
+
where.AND.push({
|
|
148
|
+
OR: [{ assignedCommercialId: null }, { assignedCommercialId: { in: realIds } }],
|
|
149
|
+
});
|
|
150
|
+
} else if (hasUnassigned) {
|
|
151
|
+
where.assignedCommercialId = null;
|
|
152
|
+
} else {
|
|
153
|
+
where.assignedCommercialId = realIds.length === 1 ? realIds[0] : { in: realIds };
|
|
154
|
+
}
|
|
155
|
+
} else if (assignedCommercialId) {
|
|
51
156
|
where.assignedCommercialId = assignedCommercialId;
|
|
52
157
|
}
|
|
53
158
|
|
|
54
|
-
if (
|
|
159
|
+
if (assignedTeleproIds) {
|
|
160
|
+
const ids = assignedTeleproIds.split(',').filter(Boolean);
|
|
161
|
+
const hasUnassigned = ids.includes('UNASSIGNED');
|
|
162
|
+
const realIds = ids.filter((id) => id !== 'UNASSIGNED');
|
|
163
|
+
if (hasUnassigned && realIds.length > 0) {
|
|
164
|
+
where.AND = where.AND || [];
|
|
165
|
+
where.AND.push({
|
|
166
|
+
OR: [{ assignedTeleproId: null }, { assignedTeleproId: { in: realIds } }],
|
|
167
|
+
});
|
|
168
|
+
} else if (hasUnassigned) {
|
|
169
|
+
where.assignedTeleproId = null;
|
|
170
|
+
} else {
|
|
171
|
+
where.assignedTeleproId = realIds.length === 1 ? realIds[0] : { in: realIds };
|
|
172
|
+
}
|
|
173
|
+
} else if (assignedTeleproId) {
|
|
55
174
|
where.assignedTeleproId = assignedTeleproId;
|
|
56
175
|
}
|
|
57
176
|
|
|
58
|
-
if (
|
|
177
|
+
if (origins) {
|
|
178
|
+
const vals = origins.split(',').filter(Boolean);
|
|
179
|
+
where.origin = vals.length === 1 ? vals[0] : { in: vals };
|
|
180
|
+
} else if (origin) {
|
|
59
181
|
where.origin = origin;
|
|
60
182
|
}
|
|
61
183
|
|
|
62
|
-
// Filtres de date pour createdAt
|
|
63
184
|
if (createdAtStart || createdAtEnd) {
|
|
64
185
|
where.createdAt = {};
|
|
65
186
|
if (createdAtStart) {
|
|
@@ -69,7 +190,6 @@ export async function GET(request: NextRequest) {
|
|
|
69
190
|
}
|
|
70
191
|
}
|
|
71
192
|
if (createdAtEnd) {
|
|
72
|
-
// Ajouter 23h59m59s pour inclure toute la journée
|
|
73
193
|
const endDate = new Date(createdAtEnd);
|
|
74
194
|
if (!isNaN(endDate.getTime())) {
|
|
75
195
|
endDate.setHours(23, 59, 59, 999);
|
|
@@ -78,7 +198,6 @@ export async function GET(request: NextRequest) {
|
|
|
78
198
|
}
|
|
79
199
|
}
|
|
80
200
|
|
|
81
|
-
// Filtres de date pour updatedAt
|
|
82
201
|
if (updatedAtStart || updatedAtEnd) {
|
|
83
202
|
where.updatedAt = {};
|
|
84
203
|
if (updatedAtStart) {
|
|
@@ -88,7 +207,6 @@ export async function GET(request: NextRequest) {
|
|
|
88
207
|
}
|
|
89
208
|
}
|
|
90
209
|
if (updatedAtEnd) {
|
|
91
|
-
// Ajouter 23h59m59s pour inclure toute la journée
|
|
92
210
|
const endDate = new Date(updatedAtEnd);
|
|
93
211
|
if (!isNaN(endDate.getTime())) {
|
|
94
212
|
endDate.setHours(23, 59, 59, 999);
|
|
@@ -97,21 +215,20 @@ export async function GET(request: NextRequest) {
|
|
|
97
215
|
}
|
|
98
216
|
}
|
|
99
217
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
218
|
+
// Determine sort order: explicit param > view config > default
|
|
219
|
+
let orderBy: any = { createdAt: 'desc' as const };
|
|
220
|
+
if (sortFieldParam && sortDirParam) {
|
|
221
|
+
orderBy = { [sortFieldParam]: sortDirParam };
|
|
222
|
+
} else if (viewSortConfig) {
|
|
223
|
+
orderBy = { [viewSortConfig.field]: viewSortConfig.direction };
|
|
224
|
+
}
|
|
103
225
|
|
|
104
226
|
const [contacts, total] = await Promise.all([
|
|
105
227
|
prisma.contact.findMany({
|
|
106
|
-
where
|
|
107
|
-
...where,
|
|
108
|
-
isCompany: false,
|
|
109
|
-
},
|
|
228
|
+
where,
|
|
110
229
|
include: {
|
|
111
230
|
status: true,
|
|
112
|
-
|
|
113
|
-
select: { id: true, firstName: true, lastName: true, isCompany: true },
|
|
114
|
-
},
|
|
231
|
+
company: { select: { id: true, name: true } },
|
|
115
232
|
assignedCommercial: {
|
|
116
233
|
select: { id: true, name: true, email: true },
|
|
117
234
|
},
|
|
@@ -121,12 +238,34 @@ export async function GET(request: NextRequest) {
|
|
|
121
238
|
createdBy: {
|
|
122
239
|
select: { id: true, name: true, email: true },
|
|
123
240
|
},
|
|
241
|
+
tourLinks: {
|
|
242
|
+
include: {
|
|
243
|
+
tour: {
|
|
244
|
+
select: {
|
|
245
|
+
id: true,
|
|
246
|
+
number: true,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
transactions: {
|
|
252
|
+
select: {
|
|
253
|
+
id: true,
|
|
254
|
+
status: true,
|
|
255
|
+
totalAmountCents: true,
|
|
256
|
+
createdAt: true,
|
|
257
|
+
},
|
|
258
|
+
orderBy: {
|
|
259
|
+
createdAt: 'desc',
|
|
260
|
+
},
|
|
261
|
+
take: 1,
|
|
262
|
+
},
|
|
124
263
|
},
|
|
125
|
-
orderBy
|
|
264
|
+
orderBy,
|
|
126
265
|
skip,
|
|
127
266
|
take: limit,
|
|
128
267
|
}),
|
|
129
|
-
prisma.contact.count({ where
|
|
268
|
+
prisma.contact.count({ where }),
|
|
130
269
|
]);
|
|
131
270
|
|
|
132
271
|
return NextResponse.json({
|
|
@@ -155,7 +294,24 @@ export async function POST(request: NextRequest) {
|
|
|
155
294
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
156
295
|
}
|
|
157
296
|
|
|
158
|
-
const
|
|
297
|
+
const canCreate = await checkPermission('contacts.create');
|
|
298
|
+
if (!canCreate) {
|
|
299
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const json = await request.json();
|
|
303
|
+
const parseResult = createContactSchema.safeParse(json);
|
|
304
|
+
|
|
305
|
+
if (!parseResult.success) {
|
|
306
|
+
return NextResponse.json(
|
|
307
|
+
{
|
|
308
|
+
error: 'Données invalides',
|
|
309
|
+
details: parseResult.error.flatten(),
|
|
310
|
+
},
|
|
311
|
+
{ status: 400 },
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
159
315
|
const {
|
|
160
316
|
civility,
|
|
161
317
|
firstName,
|
|
@@ -167,19 +323,13 @@ export async function POST(request: NextRequest) {
|
|
|
167
323
|
city,
|
|
168
324
|
postalCode,
|
|
169
325
|
origin,
|
|
170
|
-
companyName,
|
|
171
|
-
isCompany,
|
|
172
326
|
companyId,
|
|
327
|
+
jobTitle,
|
|
173
328
|
statusId,
|
|
174
329
|
closingReason,
|
|
175
330
|
assignedCommercialId,
|
|
176
331
|
assignedTeleproId,
|
|
177
|
-
} =
|
|
178
|
-
|
|
179
|
-
// Validation
|
|
180
|
-
if (!phone) {
|
|
181
|
-
return NextResponse.json({ error: 'Le téléphone est obligatoire' }, { status: 400 });
|
|
182
|
-
}
|
|
332
|
+
} = parseResult.data;
|
|
183
333
|
|
|
184
334
|
// Vérifier si c'est un doublon (nom, prénom ET email)
|
|
185
335
|
const duplicateContactId = await handleContactDuplicate(
|
|
@@ -196,9 +346,7 @@ export async function POST(request: NextRequest) {
|
|
|
196
346
|
where: { id: duplicateContactId },
|
|
197
347
|
include: {
|
|
198
348
|
status: true,
|
|
199
|
-
|
|
200
|
-
select: { id: true, firstName: true, lastName: true, isCompany: true },
|
|
201
|
-
},
|
|
349
|
+
company: { select: { id: true, name: true } },
|
|
202
350
|
assignedCommercial: {
|
|
203
351
|
select: { id: true, name: true, email: true },
|
|
204
352
|
},
|
|
@@ -216,7 +364,7 @@ export async function POST(request: NextRequest) {
|
|
|
216
364
|
// Sinon, créer un nouveau contact
|
|
217
365
|
const contact = await prisma.contact.create({
|
|
218
366
|
data: {
|
|
219
|
-
civility: civility
|
|
367
|
+
civility: civility ?? null,
|
|
220
368
|
firstName: firstName || null,
|
|
221
369
|
lastName: lastName || null,
|
|
222
370
|
phone: normalizePhoneNumber(phone),
|
|
@@ -226,9 +374,8 @@ export async function POST(request: NextRequest) {
|
|
|
226
374
|
city: city || null,
|
|
227
375
|
postalCode: postalCode || null,
|
|
228
376
|
origin: origin || null,
|
|
229
|
-
companyName: companyName || null,
|
|
230
|
-
isCompany: isCompany === true,
|
|
231
377
|
companyId: companyId || null,
|
|
378
|
+
jobTitle: jobTitle || null,
|
|
232
379
|
statusId: statusId || null,
|
|
233
380
|
closingReason: closingReason || null,
|
|
234
381
|
assignedCommercialId: assignedCommercialId || null,
|
|
@@ -252,9 +399,7 @@ export async function POST(request: NextRequest) {
|
|
|
252
399
|
},
|
|
253
400
|
include: {
|
|
254
401
|
status: true,
|
|
255
|
-
|
|
256
|
-
select: { id: true, firstName: true, lastName: true, isCompany: true },
|
|
257
|
-
},
|
|
402
|
+
company: { select: { id: true, name: true } },
|
|
258
403
|
assignedCommercial: {
|
|
259
404
|
select: { id: true, name: true, email: true },
|
|
260
405
|
},
|
|
@@ -278,6 +423,14 @@ export async function POST(request: NextRequest) {
|
|
|
278
423
|
return NextResponse.json(contact, { status: 201 });
|
|
279
424
|
} catch (error: any) {
|
|
280
425
|
console.error('Erreur lors de la création du contact:', error);
|
|
281
|
-
return NextResponse.json(
|
|
426
|
+
return NextResponse.json(
|
|
427
|
+
{
|
|
428
|
+
error:
|
|
429
|
+
process.env.NODE_ENV === 'development'
|
|
430
|
+
? error.message || 'Erreur serveur'
|
|
431
|
+
: 'Erreur serveur',
|
|
432
|
+
},
|
|
433
|
+
{ status: 500 },
|
|
434
|
+
);
|
|
282
435
|
}
|
|
283
436
|
}
|
|
@@ -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 });
|