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,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useState, useCallback, useRef, useMemo } from 'react';
|
|
4
|
-
import
|
|
4
|
+
import dynamic from 'next/dynamic';
|
|
5
|
+
import { useContainerWidth, type Layout, type LayoutItem } from 'react-grid-layout';
|
|
5
6
|
import { Plus, LayoutDashboard, RotateCcw, ShieldAlert } from 'lucide-react';
|
|
6
7
|
import { PageHeader } from '@/components/page-header';
|
|
7
8
|
import { WidgetWrapper } from '@/components/dashboard/widget-wrapper';
|
|
@@ -9,16 +10,55 @@ import { AddWidgetDialog } from '@/components/dashboard/add-widget-dialog';
|
|
|
9
10
|
import { DashboardColorPicker } from '@/components/dashboard/color-picker';
|
|
10
11
|
import { useUserRole } from '@/hooks/use-user-role';
|
|
11
12
|
import { DashboardThemeProvider, useDashboardTheme } from '@/contexts/dashboard-theme-context';
|
|
12
|
-
import { StatCard } from '@/components/dashboard/stat-card';
|
|
13
|
-
import { ContactsChart } from '@/components/dashboard/contacts-chart';
|
|
14
|
-
import { ActivityChart } from '@/components/dashboard/activity-chart';
|
|
15
|
-
import { StatusDistributionChart } from '@/components/dashboard/status-distribution-chart';
|
|
16
|
-
import { TasksPieChart } from '@/components/dashboard/tasks-pie-chart';
|
|
17
|
-
import { UpcomingTasksList } from '@/components/dashboard/upcoming-tasks-list';
|
|
18
|
-
import { RecentActivity } from '@/components/dashboard/recent-activity';
|
|
19
|
-
import { TopContactsList } from '@/components/dashboard/top-contacts-list';
|
|
20
|
-
import { InteractionsByTypeChart } from '@/components/dashboard/interactions-by-type-chart';
|
|
21
13
|
import { getWidgetDefinition } from '@/lib/widget-registry';
|
|
14
|
+
import { useAppToast } from '@/contexts/app-toast-context';
|
|
15
|
+
import { devToast } from '@/lib/utils';
|
|
16
|
+
|
|
17
|
+
const GridLayout = dynamic(() => import('react-grid-layout').then((mod) => mod.GridLayout), {
|
|
18
|
+
ssr: false,
|
|
19
|
+
});
|
|
20
|
+
const StatCard = dynamic(
|
|
21
|
+
() => import('@/components/dashboard/stat-card').then((mod) => mod.StatCard),
|
|
22
|
+
{ ssr: false },
|
|
23
|
+
);
|
|
24
|
+
const ContactsChart = dynamic(
|
|
25
|
+
() => import('@/components/dashboard/contacts-chart').then((mod) => mod.ContactsChart),
|
|
26
|
+
{ ssr: false },
|
|
27
|
+
);
|
|
28
|
+
const ActivityChart = dynamic(
|
|
29
|
+
() => import('@/components/dashboard/activity-chart').then((mod) => mod.ActivityChart),
|
|
30
|
+
{ ssr: false },
|
|
31
|
+
);
|
|
32
|
+
const StatusDistributionChart = dynamic(
|
|
33
|
+
() =>
|
|
34
|
+
import('@/components/dashboard/status-distribution-chart').then(
|
|
35
|
+
(mod) => mod.StatusDistributionChart,
|
|
36
|
+
),
|
|
37
|
+
{ ssr: false },
|
|
38
|
+
);
|
|
39
|
+
const TasksPieChart = dynamic(
|
|
40
|
+
() => import('@/components/dashboard/tasks-pie-chart').then((mod) => mod.TasksPieChart),
|
|
41
|
+
{ ssr: false },
|
|
42
|
+
);
|
|
43
|
+
const UpcomingTasksList = dynamic(
|
|
44
|
+
() => import('@/components/dashboard/upcoming-tasks-list').then((mod) => mod.UpcomingTasksList),
|
|
45
|
+
{ ssr: false },
|
|
46
|
+
);
|
|
47
|
+
const RecentActivity = dynamic(
|
|
48
|
+
() => import('@/components/dashboard/recent-activity').then((mod) => mod.RecentActivity),
|
|
49
|
+
{ ssr: false },
|
|
50
|
+
);
|
|
51
|
+
const TopContactsList = dynamic(
|
|
52
|
+
() => import('@/components/dashboard/top-contacts-list').then((mod) => mod.TopContactsList),
|
|
53
|
+
{ ssr: false },
|
|
54
|
+
);
|
|
55
|
+
const InteractionsByTypeChart = dynamic(
|
|
56
|
+
() =>
|
|
57
|
+
import('@/components/dashboard/interactions-by-type-chart').then(
|
|
58
|
+
(mod) => mod.InteractionsByTypeChart,
|
|
59
|
+
),
|
|
60
|
+
{ ssr: false },
|
|
61
|
+
);
|
|
22
62
|
|
|
23
63
|
interface DashboardWidget {
|
|
24
64
|
id: string;
|
|
@@ -89,6 +129,7 @@ export default function DashboardPage() {
|
|
|
89
129
|
}
|
|
90
130
|
|
|
91
131
|
function DashboardContent() {
|
|
132
|
+
const toast = useAppToast();
|
|
92
133
|
const [widgets, setWidgets] = useState<DashboardWidget[]>([]);
|
|
93
134
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
|
94
135
|
const [loading, setLoading] = useState(true);
|
|
@@ -98,7 +139,6 @@ function DashboardContent() {
|
|
|
98
139
|
const { hasPermission, isLoading: permissionsLoading } = useUserRole();
|
|
99
140
|
const { theme } = useDashboardTheme();
|
|
100
141
|
|
|
101
|
-
// Permissions dashboard
|
|
102
142
|
const canViewDashboard = hasPermission('dashboard.view');
|
|
103
143
|
const canManageWidgets = hasPermission('dashboard.widgets.manage');
|
|
104
144
|
const canResetDashboard = hasPermission('dashboard.widgets.reset');
|
|
@@ -108,47 +148,30 @@ function DashboardContent() {
|
|
|
108
148
|
mounted,
|
|
109
149
|
} = useContainerWidth({ initialWidth: 1200 });
|
|
110
150
|
|
|
111
|
-
// Nombre de colonnes dynamique basé sur la largeur réelle
|
|
112
151
|
const cols = useMemo(() => {
|
|
113
152
|
if (containerWidth < 768) return 2;
|
|
114
153
|
return 12;
|
|
115
154
|
}, [containerWidth]);
|
|
116
155
|
|
|
117
|
-
// Charger widgets et stats
|
|
118
156
|
useEffect(() => {
|
|
119
157
|
async function fetchData() {
|
|
120
158
|
try {
|
|
121
|
-
const widgetsRes = await
|
|
122
|
-
|
|
159
|
+
const [widgetsRes, statsRes] = await Promise.all([
|
|
160
|
+
fetch('/api/dashboard/widgets'),
|
|
161
|
+
fetch('/api/dashboard/stats'),
|
|
162
|
+
]);
|
|
123
163
|
|
|
124
164
|
if (widgetsRes.ok) {
|
|
125
|
-
|
|
165
|
+
setWidgets(await widgetsRes.json());
|
|
126
166
|
}
|
|
127
167
|
|
|
128
|
-
// Initialiser le layout par défaut uniquement à la toute première visite
|
|
129
|
-
// (pas de widgets ET jamais initialisé auparavant)
|
|
130
|
-
const hasBeenInitialized = localStorage.getItem('dashboard_initialized');
|
|
131
|
-
if (widgetsData.length === 0 && !hasBeenInitialized) {
|
|
132
|
-
const initRes = await fetch('/api/dashboard/widgets', {
|
|
133
|
-
method: 'POST',
|
|
134
|
-
headers: { 'Content-Type': 'application/json' },
|
|
135
|
-
body: JSON.stringify({ initDefault: true }),
|
|
136
|
-
});
|
|
137
|
-
if (initRes.ok) {
|
|
138
|
-
widgetsData = await initRes.json();
|
|
139
|
-
}
|
|
140
|
-
localStorage.setItem('dashboard_initialized', 'true');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
setWidgets(widgetsData);
|
|
144
|
-
|
|
145
|
-
const statsRes = await fetch('/api/dashboard/stats');
|
|
146
168
|
if (statsRes.ok) {
|
|
147
|
-
|
|
148
|
-
setStats(statsData);
|
|
169
|
+
setStats(await statsRes.json());
|
|
149
170
|
}
|
|
150
171
|
} catch (err) {
|
|
151
|
-
|
|
172
|
+
const message = err instanceof Error ? err.message : 'Une erreur est survenue';
|
|
173
|
+
setError(message);
|
|
174
|
+
toast.error(devToast('Une erreur est survenue lors du chargement du tableau de bord', err));
|
|
152
175
|
} finally {
|
|
153
176
|
setLoading(false);
|
|
154
177
|
}
|
|
@@ -157,7 +180,6 @@ function DashboardContent() {
|
|
|
157
180
|
fetchData();
|
|
158
181
|
}, []);
|
|
159
182
|
|
|
160
|
-
// Sauvegarder les positions avec debounce
|
|
161
183
|
const saveLayout = useCallback((updatedWidgets: DashboardWidget[]) => {
|
|
162
184
|
if (saveTimeoutRef.current) {
|
|
163
185
|
clearTimeout(saveTimeoutRef.current);
|
|
@@ -180,13 +202,11 @@ function DashboardContent() {
|
|
|
180
202
|
});
|
|
181
203
|
} catch (err) {
|
|
182
204
|
console.error('Erreur sauvegarde layout:', err);
|
|
205
|
+
toast.error(devToast('Impossible de sauvegarder la disposition du tableau de bord. Veuillez réessayer.', err));
|
|
183
206
|
}
|
|
184
207
|
}, 500);
|
|
185
208
|
}, []);
|
|
186
209
|
|
|
187
|
-
// Gérer le changement de layout (drag/resize)
|
|
188
|
-
// Ne sauvegarde en base que quand on est en mode desktop (12 cols)
|
|
189
|
-
// pour ne pas écraser les positions desktop avec les positions mobile
|
|
190
210
|
const handleLayoutChange = useCallback(
|
|
191
211
|
(layout: Layout) => {
|
|
192
212
|
if (cols !== 12) return;
|
|
@@ -211,7 +231,6 @@ function DashboardContent() {
|
|
|
211
231
|
[widgets, saveLayout, cols],
|
|
212
232
|
);
|
|
213
233
|
|
|
214
|
-
// Ajouter un widget
|
|
215
234
|
const handleAddWidget = useCallback(async (type: string, w: number, h: number) => {
|
|
216
235
|
try {
|
|
217
236
|
const res = await fetch('/api/dashboard/widgets', {
|
|
@@ -226,12 +245,12 @@ function DashboardContent() {
|
|
|
226
245
|
}
|
|
227
246
|
} catch (err) {
|
|
228
247
|
console.error('Erreur ajout widget:', err);
|
|
248
|
+
toast.error(devToast('Impossible d\'ajouter le widget. Veuillez réessayer.', err));
|
|
229
249
|
}
|
|
230
250
|
|
|
231
251
|
setShowAddDialog(false);
|
|
232
252
|
}, []);
|
|
233
253
|
|
|
234
|
-
// Supprimer un widget
|
|
235
254
|
const handleRemoveWidget = useCallback(async (widgetId: string) => {
|
|
236
255
|
try {
|
|
237
256
|
const res = await fetch(`/api/dashboard/widgets/${widgetId}`, {
|
|
@@ -243,40 +262,32 @@ function DashboardContent() {
|
|
|
243
262
|
}
|
|
244
263
|
} catch (err) {
|
|
245
264
|
console.error('Erreur suppression widget:', err);
|
|
265
|
+
toast.error(devToast('Impossible de retirer le widget. Veuillez réessayer.', err));
|
|
246
266
|
}
|
|
247
267
|
}, []);
|
|
248
268
|
|
|
249
|
-
// Réinitialiser le layout par défaut
|
|
250
269
|
const handleResetLayout = useCallback(async () => {
|
|
251
270
|
try {
|
|
252
|
-
|
|
253
|
-
await Promise.all(
|
|
254
|
-
widgets.map((w) => fetch(`/api/dashboard/widgets/${w.id}`, { method: 'DELETE' })),
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
// Recréer le layout par défaut
|
|
258
|
-
const initRes = await fetch('/api/dashboard/widgets', {
|
|
271
|
+
const res = await fetch('/api/dashboard/widgets', {
|
|
259
272
|
method: 'POST',
|
|
260
273
|
headers: { 'Content-Type': 'application/json' },
|
|
261
274
|
body: JSON.stringify({ initDefault: true }),
|
|
262
275
|
});
|
|
263
276
|
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
setWidgets(newWidgets);
|
|
277
|
+
if (res.ok) {
|
|
278
|
+
setWidgets(await res.json());
|
|
267
279
|
}
|
|
268
280
|
} catch (err) {
|
|
269
281
|
console.error('Erreur réinitialisation layout:', err);
|
|
282
|
+
toast.error(devToast('Impossible de réinitialiser le tableau de bord. Veuillez réessayer.', err));
|
|
270
283
|
}
|
|
271
|
-
}, [
|
|
284
|
+
}, []);
|
|
272
285
|
|
|
273
|
-
// Construire le layout adapté au nombre de colonnes actuel
|
|
274
286
|
const gridLayout = useMemo((): Layout => {
|
|
275
287
|
return widgets.map((widget) => {
|
|
276
288
|
const def = getWidgetDefinition(widget.type);
|
|
277
289
|
|
|
278
290
|
if (cols === 2) {
|
|
279
|
-
// Mobile : stat cards (w <= 3 en 12 cols) → 1 col, le reste → 2 cols (pleine largeur)
|
|
280
291
|
const isSmallWidget = widget.w <= 4;
|
|
281
292
|
const mobileW = isSmallWidget ? 1 : 2;
|
|
282
293
|
return {
|
|
@@ -291,7 +302,6 @@ function DashboardContent() {
|
|
|
291
302
|
};
|
|
292
303
|
}
|
|
293
304
|
|
|
294
|
-
// Desktop : layout stocké en base (grille 12 colonnes)
|
|
295
305
|
return {
|
|
296
306
|
i: widget.id,
|
|
297
307
|
x: widget.x,
|
|
@@ -305,7 +315,6 @@ function DashboardContent() {
|
|
|
305
315
|
});
|
|
306
316
|
}, [widgets, cols]);
|
|
307
317
|
|
|
308
|
-
// Rendu du contenu d'un widget selon son type
|
|
309
318
|
const renderWidgetContent = useCallback(
|
|
310
319
|
(widget: DashboardWidget) => {
|
|
311
320
|
if (!stats) return null;
|
|
@@ -377,7 +386,6 @@ function DashboardContent() {
|
|
|
377
386
|
[stats],
|
|
378
387
|
);
|
|
379
388
|
|
|
380
|
-
// Contenu intérieur selon l'état
|
|
381
389
|
const renderContent = () => {
|
|
382
390
|
if (loading || permissionsLoading) {
|
|
383
391
|
return (
|
|
@@ -398,11 +406,12 @@ function DashboardContent() {
|
|
|
398
406
|
|
|
399
407
|
if (!canViewDashboard) {
|
|
400
408
|
return (
|
|
401
|
-
<div className="flex h-96 flex-col items-center justify-center rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50">
|
|
409
|
+
<div className="ui-fade-in flex h-96 flex-col items-center justify-center rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50">
|
|
402
410
|
<ShieldAlert className="h-12 w-12 text-gray-300" />
|
|
403
411
|
<h3 className="mt-4 text-base font-medium text-gray-600">Accès restreint</h3>
|
|
404
412
|
<p className="mt-1 text-center text-sm text-gray-400">
|
|
405
|
-
Vous n'avez pas la permission d'accéder au tableau de bord
|
|
413
|
+
Vous n'avez pas la permission d'accéder au tableau de bord.
|
|
414
|
+
<br />
|
|
406
415
|
Contactez votre administrateur pour obtenir les droits nécessaires.
|
|
407
416
|
</p>
|
|
408
417
|
</div>
|
|
@@ -410,17 +419,12 @@ function DashboardContent() {
|
|
|
410
419
|
}
|
|
411
420
|
|
|
412
421
|
if (error) {
|
|
413
|
-
return
|
|
414
|
-
<div className="flex h-96 items-center justify-center p-4">
|
|
415
|
-
<p className="text-sm text-red-500">{error}</p>
|
|
416
|
-
</div>
|
|
417
|
-
);
|
|
422
|
+
return null;
|
|
418
423
|
}
|
|
419
424
|
|
|
420
425
|
return null;
|
|
421
426
|
};
|
|
422
427
|
|
|
423
|
-
// CSS variables pour le thème
|
|
424
428
|
const themeVars = {
|
|
425
429
|
'--dash-50': theme.hex[50],
|
|
426
430
|
'--dash-100': theme.hex[100],
|
|
@@ -444,7 +448,7 @@ function DashboardContent() {
|
|
|
444
448
|
{widgets.length > 0 && canResetDashboard && (
|
|
445
449
|
<button
|
|
446
450
|
onClick={handleResetLayout}
|
|
447
|
-
className="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-600 shadow-sm transition-
|
|
451
|
+
className="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-200 bg-white px-4 py-2.5 text-sm font-medium text-gray-600 shadow-sm transition-[background-color,color,box-shadow,transform] duration-150 hover:bg-gray-50 hover:text-gray-900 hover:shadow-md active:scale-[0.98]"
|
|
448
452
|
>
|
|
449
453
|
<RotateCcw className="h-4 w-4" />
|
|
450
454
|
Réinitialiser
|
|
@@ -453,7 +457,7 @@ function DashboardContent() {
|
|
|
453
457
|
{canManageWidgets && (
|
|
454
458
|
<button
|
|
455
459
|
onClick={() => setShowAddDialog(true)}
|
|
456
|
-
className="dash-btn inline-flex cursor-pointer items-center gap-2 rounded-xl px-4 py-2.5 text-sm font-medium shadow-sm transition-
|
|
460
|
+
className="dash-btn inline-flex cursor-pointer items-center gap-2 rounded-xl px-4 py-2.5 text-sm font-medium shadow-sm transition-[box-shadow,transform] duration-150 hover:shadow-md active:scale-[0.98]"
|
|
457
461
|
>
|
|
458
462
|
<Plus className="h-4 w-4" />
|
|
459
463
|
Ajouter un Widget
|
|
@@ -464,17 +468,16 @@ function DashboardContent() {
|
|
|
464
468
|
}
|
|
465
469
|
/>
|
|
466
470
|
|
|
467
|
-
{/* Le ref est TOUJOURS dans le DOM pour que useContainerWidth mesure correctement */}
|
|
468
471
|
<div ref={containerRef} className="p-4 sm:p-6">
|
|
469
472
|
{renderContent()}
|
|
470
473
|
{!loading && !permissionsLoading && !error && canViewDashboard && widgets.length === 0 && (
|
|
471
|
-
<div className="flex h-96 flex-col items-center justify-center rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50">
|
|
474
|
+
<div className="ui-fade-in flex h-96 flex-col items-center justify-center rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50">
|
|
472
475
|
<LayoutDashboard className="h-12 w-12 text-gray-300" />
|
|
473
476
|
<h3 className="mt-4 text-base font-medium text-gray-600">Aucun widget configuré</h3>
|
|
474
477
|
<p className="mt-1 text-sm text-gray-400">
|
|
475
478
|
{canManageWidgets
|
|
476
479
|
? 'Ajoutez des widgets pour personnaliser votre tableau de bord'
|
|
477
|
-
:
|
|
480
|
+
: "Aucun widget n'est configuré. Contactez votre administrateur."}
|
|
478
481
|
</p>
|
|
479
482
|
{canManageWidgets && (
|
|
480
483
|
<button
|
|
@@ -488,38 +491,49 @@ function DashboardContent() {
|
|
|
488
491
|
</div>
|
|
489
492
|
)}
|
|
490
493
|
|
|
491
|
-
{!loading &&
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
494
|
+
{!loading &&
|
|
495
|
+
!permissionsLoading &&
|
|
496
|
+
!error &&
|
|
497
|
+
canViewDashboard &&
|
|
498
|
+
widgets.length > 0 &&
|
|
499
|
+
mounted && (
|
|
500
|
+
<GridLayout
|
|
501
|
+
className="layout ui-fade-in"
|
|
502
|
+
width={containerWidth}
|
|
503
|
+
layout={gridLayout}
|
|
504
|
+
gridConfig={{
|
|
505
|
+
cols,
|
|
506
|
+
rowHeight: 60,
|
|
507
|
+
margin: [16, 16] as const,
|
|
508
|
+
containerPadding: [0, 0] as const,
|
|
509
|
+
maxRows: Infinity,
|
|
510
|
+
}}
|
|
511
|
+
onLayoutChange={handleLayoutChange}
|
|
512
|
+
dragConfig={{
|
|
513
|
+
handle: '.drag-handle',
|
|
514
|
+
enabled: canManageWidgets,
|
|
515
|
+
threshold: 3,
|
|
516
|
+
bounded: false,
|
|
517
|
+
}}
|
|
518
|
+
resizeConfig={{ enabled: canManageWidgets, handles: ['se'] }}
|
|
519
|
+
>
|
|
520
|
+
{widgets.map((widget) => (
|
|
521
|
+
<div key={widget.id}>
|
|
522
|
+
<WidgetWrapper
|
|
523
|
+
onRemove={
|
|
524
|
+
canManageWidgets
|
|
525
|
+
? () => {
|
|
526
|
+
handleRemoveWidget(widget.id);
|
|
527
|
+
}
|
|
528
|
+
: undefined
|
|
529
|
+
}
|
|
530
|
+
>
|
|
531
|
+
{renderWidgetContent(widget)}
|
|
532
|
+
</WidgetWrapper>
|
|
533
|
+
</div>
|
|
534
|
+
))}
|
|
535
|
+
</GridLayout>
|
|
536
|
+
)}
|
|
523
537
|
</div>
|
|
524
538
|
|
|
525
539
|
<AddWidgetDialog
|