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,64 +1,56 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
Bar,
|
|
5
|
-
BarChart,
|
|
6
|
-
ResponsiveContainer,
|
|
7
|
-
Tooltip,
|
|
8
|
-
XAxis,
|
|
9
|
-
YAxis,
|
|
10
|
-
CartesianGrid,
|
|
11
|
-
Legend,
|
|
12
|
-
} from 'recharts';
|
|
13
|
-
import { useDashboardTheme } from '@/contexts/dashboard-theme-context';
|
|
3
|
+
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis, Legend } from 'recharts';
|
|
14
4
|
|
|
15
5
|
interface ActivityChartProps {
|
|
16
|
-
|
|
6
|
+
data: Array<{ date: string; interactions: number; tasks: number }>;
|
|
17
7
|
}
|
|
18
8
|
|
|
19
|
-
export function ActivityChart({ data }:
|
|
20
|
-
const { theme } = useDashboardTheme();
|
|
21
|
-
|
|
9
|
+
export function ActivityChart({ data }: ActivityChartProps) {
|
|
22
10
|
return (
|
|
23
|
-
<div className="
|
|
11
|
+
<div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
|
24
12
|
<div className="mb-4">
|
|
25
|
-
<h3 className="text-
|
|
26
|
-
<p className="mt-
|
|
13
|
+
<h3 className="text-lg font-bold text-gray-900">Activité des 7 Derniers Jours</h3>
|
|
14
|
+
<p className="mt-1 text-sm text-gray-500">Interactions et tâches créées</p>
|
|
27
15
|
</div>
|
|
28
|
-
<div className="
|
|
16
|
+
<div className="h-[300px]">
|
|
29
17
|
<ResponsiveContainer width="100%" height="100%">
|
|
30
|
-
<BarChart
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
18
|
+
<BarChart data={data} barCategoryGap="20%">
|
|
19
|
+
<defs>
|
|
20
|
+
<linearGradient id="interactionsGradient" x1="0" y1="0" x2="0" y2="1">
|
|
21
|
+
<stop offset="0%" stopColor="#6366f1" stopOpacity={1} />
|
|
22
|
+
<stop offset="100%" stopColor="#4f46e5" stopOpacity={0.8} />
|
|
23
|
+
</linearGradient>
|
|
24
|
+
<linearGradient id="tasksGradient" x1="0" y1="0" x2="0" y2="1">
|
|
25
|
+
<stop offset="0%" stopColor="#8b5cf6" stopOpacity={1} />
|
|
26
|
+
<stop offset="100%" stopColor="#7c3aed" stopOpacity={0.8} />
|
|
27
|
+
</linearGradient>
|
|
28
|
+
</defs>
|
|
36
29
|
<XAxis
|
|
37
30
|
dataKey="date"
|
|
38
|
-
stroke="#
|
|
39
|
-
fontSize={
|
|
31
|
+
stroke="#9ca3af"
|
|
32
|
+
fontSize={12}
|
|
40
33
|
tickLine={false}
|
|
41
34
|
axisLine={false}
|
|
42
|
-
dy={8}
|
|
43
35
|
/>
|
|
44
|
-
<YAxis stroke="#
|
|
36
|
+
<YAxis stroke="#9ca3af" fontSize={12} tickLine={false} axisLine={false} />
|
|
45
37
|
<Tooltip
|
|
46
38
|
contentStyle={{
|
|
47
|
-
backgroundColor: '
|
|
48
|
-
border: '1px solid #
|
|
39
|
+
backgroundColor: 'white',
|
|
40
|
+
border: '1px solid #e5e7eb',
|
|
49
41
|
borderRadius: '12px',
|
|
50
|
-
boxShadow: '0 4px
|
|
51
|
-
fontSize: '13px',
|
|
42
|
+
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
52
43
|
}}
|
|
53
44
|
labelStyle={{ color: '#374151', fontWeight: 600 }}
|
|
54
45
|
/>
|
|
55
|
-
<Legend
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
<Legend wrapperStyle={{ paddingTop: '20px' }} iconType="circle" />
|
|
47
|
+
<Bar
|
|
48
|
+
dataKey="interactions"
|
|
49
|
+
fill="url(#interactionsGradient)"
|
|
50
|
+
radius={[8, 8, 0, 0]}
|
|
51
|
+
name="Interactions"
|
|
59
52
|
/>
|
|
60
|
-
<Bar dataKey="
|
|
61
|
-
<Bar dataKey="tasks" fill={theme.hex[300]} radius={[6, 6, 0, 0]} name="Tâches" />
|
|
53
|
+
<Bar dataKey="tasks" fill="url(#tasksGradient)" radius={[8, 8, 0, 0]} name="Tâches" />
|
|
62
54
|
</BarChart>
|
|
63
55
|
</ResponsiveContainer>
|
|
64
56
|
</div>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import dynamic from 'next/dynamic';
|
|
4
|
+
import { Users, CheckCircle2, Clock, TrendingUp } from 'lucide-react';
|
|
5
|
+
import { StatCard } from '@/components/dashboard/stat-card';
|
|
6
|
+
import { UpcomingTasksList } from '@/components/dashboard/upcoming-tasks-list';
|
|
7
|
+
import { PageHeader } from '@/components/page-header';
|
|
8
|
+
import { ProtectedPage } from '@/components/protected-page';
|
|
9
|
+
import { Skeleton } from '@/components/skeleton';
|
|
10
|
+
import type { DashboardStats } from '@/lib/dashboard-stats';
|
|
11
|
+
|
|
12
|
+
const ActivityChart = dynamic(
|
|
13
|
+
() => import('@/components/dashboard/activity-chart').then((m) => m.ActivityChart),
|
|
14
|
+
{ ssr: false, loading: () => <Skeleton className="h-80 rounded-lg" /> },
|
|
15
|
+
);
|
|
16
|
+
const TasksPieChart = dynamic(
|
|
17
|
+
() => import('@/components/dashboard/tasks-pie-chart').then((m) => m.TasksPieChart),
|
|
18
|
+
{ ssr: false, loading: () => <Skeleton className="h-80 rounded-lg" /> },
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
interface DashboardContentProps {
|
|
22
|
+
stats: DashboardStats;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function DashboardContent({ stats }: Readonly<DashboardContentProps>) {
|
|
26
|
+
return (
|
|
27
|
+
<ProtectedPage requiredPermission="analytics.view">
|
|
28
|
+
<div className="h-full">
|
|
29
|
+
<PageHeader title="Tableau de Bord" description="Vue d'ensemble de votre activité" />
|
|
30
|
+
|
|
31
|
+
<div className="space-y-6 p-4 sm:p-6">
|
|
32
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
33
|
+
<StatCard
|
|
34
|
+
title="Total Contacts"
|
|
35
|
+
value={stats.overview.totalContacts.toLocaleString('fr-FR')}
|
|
36
|
+
icon={Users}
|
|
37
|
+
trend={{
|
|
38
|
+
value: stats.overview.contactsGrowth,
|
|
39
|
+
label: 'ce mois',
|
|
40
|
+
}}
|
|
41
|
+
iconColor="text-primary"
|
|
42
|
+
iconBgColor="bg-primary/15"
|
|
43
|
+
/>
|
|
44
|
+
<StatCard
|
|
45
|
+
title="Nouveaux ce Mois"
|
|
46
|
+
value={stats.overview.contactsThisMonth.toLocaleString('fr-FR')}
|
|
47
|
+
icon={TrendingUp}
|
|
48
|
+
iconColor="text-green-600"
|
|
49
|
+
iconBgColor="bg-green-100"
|
|
50
|
+
/>
|
|
51
|
+
<StatCard
|
|
52
|
+
title="Tâches Complétées"
|
|
53
|
+
value={stats.tasks.completed.toLocaleString('fr-FR')}
|
|
54
|
+
icon={CheckCircle2}
|
|
55
|
+
iconColor="text-emerald-600"
|
|
56
|
+
iconBgColor="bg-emerald-100"
|
|
57
|
+
/>
|
|
58
|
+
<StatCard
|
|
59
|
+
title="Tâches en Attente"
|
|
60
|
+
value={stats.tasks.pending.toLocaleString('fr-FR')}
|
|
61
|
+
icon={Clock}
|
|
62
|
+
iconColor="text-blue-600"
|
|
63
|
+
iconBgColor="bg-blue-100"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div className="grid gap-6 lg:grid-cols-3">
|
|
68
|
+
<div className="lg:col-span-2">
|
|
69
|
+
<ActivityChart data={stats.activity.last7Days} />
|
|
70
|
+
</div>
|
|
71
|
+
<TasksPieChart completed={stats.tasks.completed} pending={stats.tasks.pending} />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<UpcomingTasksList tasks={stats.tasks.upcoming} />
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</ProtectedPage>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -1,63 +1,61 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { LucideIcon } from 'lucide-react';
|
|
3
4
|
import { cn } from '@/lib/utils';
|
|
4
5
|
|
|
5
6
|
interface StatCardProps {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
title: string;
|
|
8
|
+
value: string | number;
|
|
9
|
+
icon: LucideIcon;
|
|
10
|
+
trend?: {
|
|
11
|
+
value: number;
|
|
12
|
+
label: string;
|
|
11
13
|
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
iconColor?: string;
|
|
15
|
+
iconBgColor?: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export function StatCard({
|
|
17
19
|
title,
|
|
18
20
|
value,
|
|
21
|
+
icon: Icon,
|
|
19
22
|
trend,
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
iconColor = 'text-primary',
|
|
24
|
+
iconBgColor = 'bg-primary/15',
|
|
22
25
|
}: StatCardProps) {
|
|
23
26
|
return (
|
|
24
|
-
<div className="group relative
|
|
25
|
-
|
|
26
|
-
<div className=
|
|
27
|
-
|
|
28
|
-
<div className="flex items-start justify-between">
|
|
27
|
+
<div className="group relative overflow-hidden rounded-xl border border-border bg-card p-6 shadow-(--shadow-card) transition-all duration-300 hover:-translate-y-1">
|
|
28
|
+
<div className="absolute inset-0 bg-linear-to-br from-card via-card to-muted/40 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
|
29
|
+
<div className="relative flex items-center justify-between">
|
|
29
30
|
<div className="flex-1">
|
|
30
|
-
<p className="text-sm font-medium text-
|
|
31
|
-
<p className="mt-2 text-3xl font-bold
|
|
31
|
+
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
|
32
|
+
<p className="mt-2 bg-linear-to-r from-foreground to-foreground/80 bg-clip-text text-3xl font-bold text-transparent">
|
|
33
|
+
{value}
|
|
34
|
+
</p>
|
|
35
|
+
{trend && (
|
|
36
|
+
<p className="mt-2 flex items-center gap-1 text-sm">
|
|
37
|
+
<span
|
|
38
|
+
className={cn(
|
|
39
|
+
'font-semibold',
|
|
40
|
+
trend.value >= 0 ? 'text-emerald-600' : 'text-red-600',
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
{trend.value >= 0 ? '+' : ''}
|
|
44
|
+
{trend.value}%
|
|
45
|
+
</span>
|
|
46
|
+
<span className="text-muted-foreground">{trend.label}</span>
|
|
47
|
+
</p>
|
|
48
|
+
)}
|
|
32
49
|
</div>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
style={{ height: `${barH * 0.3}px`, opacity: 0.4 + (barH / 90) * 0.6 }}
|
|
41
|
-
/>
|
|
42
|
-
))}
|
|
50
|
+
<div
|
|
51
|
+
className={cn(
|
|
52
|
+
'rounded-xl bg-linear-to-br p-4 shadow-sm transition-transform duration-300 group-hover:scale-110',
|
|
53
|
+
iconBgColor,
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
<Icon className={cn('h-6 w-6', iconColor)} />
|
|
43
57
|
</div>
|
|
44
58
|
</div>
|
|
45
|
-
|
|
46
|
-
<div className="mt-3 flex items-center gap-2">
|
|
47
|
-
{trend && (
|
|
48
|
-
<span
|
|
49
|
-
className={cn(
|
|
50
|
-
'inline-flex items-center gap-0.5 rounded-full px-2 py-0.5 text-xs font-semibold',
|
|
51
|
-
trend.value >= 0 ? 'bg-emerald-50 text-emerald-600' : 'bg-red-50 text-red-600',
|
|
52
|
-
)}
|
|
53
|
-
>
|
|
54
|
-
{trend.value >= 0 ? '↑' : '↓'} {Math.abs(trend.value)}%
|
|
55
|
-
</span>
|
|
56
|
-
)}
|
|
57
|
-
{(subtitle || trend?.label) && (
|
|
58
|
-
<span className="text-xs text-gray-400">{subtitle || trend?.label}</span>
|
|
59
|
-
)}
|
|
60
|
-
</div>
|
|
61
59
|
</div>
|
|
62
60
|
);
|
|
63
61
|
}
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
|
|
4
|
-
import { useDashboardTheme } from '@/contexts/dashboard-theme-context';
|
|
5
4
|
|
|
6
5
|
interface TasksPieChartProps {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
completed: number;
|
|
7
|
+
pending: number;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
completed: '#10b981',
|
|
16
|
-
pending: theme.hex[500],
|
|
17
|
-
};
|
|
10
|
+
const COLORS = {
|
|
11
|
+
completed: '#10b981',
|
|
12
|
+
pending: '#f59e0b',
|
|
13
|
+
};
|
|
18
14
|
|
|
15
|
+
export function TasksPieChart({ completed, pending }: TasksPieChartProps) {
|
|
19
16
|
const data = [
|
|
20
17
|
{ name: 'Complétées', value: completed },
|
|
21
18
|
{ name: 'En attente', value: pending },
|
|
@@ -25,64 +22,64 @@ export function TasksPieChart({ completed, pending }: Readonly<TasksPieChartProp
|
|
|
25
22
|
const completionRate = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
26
23
|
|
|
27
24
|
return (
|
|
28
|
-
<div className="
|
|
29
|
-
<div className="mb-
|
|
30
|
-
<h3 className="text-
|
|
31
|
-
<p className="mt-
|
|
25
|
+
<div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
|
26
|
+
<div className="mb-4">
|
|
27
|
+
<h3 className="text-lg font-bold text-gray-900">Statut des Tâches</h3>
|
|
28
|
+
<p className="mt-1 text-sm text-gray-500">Répartition des tâches</p>
|
|
32
29
|
</div>
|
|
33
|
-
<div className="flex
|
|
34
|
-
<div className="relative h-[
|
|
30
|
+
<div className="flex items-center justify-center">
|
|
31
|
+
<div className="relative h-[200px] w-[200px]">
|
|
35
32
|
<ResponsiveContainer width="100%" height="100%">
|
|
36
33
|
<PieChart>
|
|
37
34
|
<Pie
|
|
38
35
|
data={data}
|
|
39
36
|
cx="50%"
|
|
40
37
|
cy="50%"
|
|
41
|
-
innerRadius={
|
|
42
|
-
outerRadius={
|
|
43
|
-
paddingAngle={
|
|
38
|
+
innerRadius={60}
|
|
39
|
+
outerRadius={80}
|
|
40
|
+
paddingAngle={2}
|
|
44
41
|
dataKey="value"
|
|
45
|
-
strokeWidth={0}
|
|
46
42
|
>
|
|
47
|
-
{data.map((entry) => (
|
|
43
|
+
{data.map((entry, index) => (
|
|
48
44
|
<Cell
|
|
49
|
-
key={
|
|
45
|
+
key={`cell-${index}`}
|
|
50
46
|
fill={entry.name === 'Complétées' ? COLORS.completed : COLORS.pending}
|
|
51
47
|
/>
|
|
52
48
|
))}
|
|
53
49
|
</Pie>
|
|
54
50
|
<Tooltip
|
|
55
51
|
contentStyle={{
|
|
56
|
-
backgroundColor: '
|
|
57
|
-
border: '1px solid #
|
|
52
|
+
backgroundColor: 'white',
|
|
53
|
+
border: '1px solid #e5e7eb',
|
|
58
54
|
borderRadius: '12px',
|
|
59
|
-
boxShadow: '0 4px
|
|
60
|
-
fontSize: '13px',
|
|
55
|
+
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
61
56
|
}}
|
|
62
57
|
/>
|
|
63
58
|
</PieChart>
|
|
64
59
|
</ResponsiveContainer>
|
|
65
60
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
66
61
|
<div className="text-center">
|
|
67
|
-
<p className="text-
|
|
68
|
-
|
|
62
|
+
<p className="bg-linear-to-r from-gray-900 to-gray-700 bg-clip-text text-3xl font-bold text-transparent">
|
|
63
|
+
{completionRate}%
|
|
64
|
+
</p>
|
|
65
|
+
<p className="text-xs text-gray-500">Complétées</p>
|
|
69
66
|
</div>
|
|
70
67
|
</div>
|
|
71
68
|
</div>
|
|
72
69
|
</div>
|
|
73
|
-
<div className="mt-
|
|
74
|
-
<div className="flex items-center gap-2 rounded-
|
|
75
|
-
<div className="h-
|
|
70
|
+
<div className="mt-4 grid grid-cols-2 gap-4">
|
|
71
|
+
<div className="flex items-center gap-2 rounded-lg bg-emerald-50 p-2">
|
|
72
|
+
<div className="h-3 w-3 rounded-full bg-emerald-500" />
|
|
76
73
|
<div>
|
|
77
|
-
<p className="text-
|
|
78
|
-
<p className="
|
|
74
|
+
<p className="text-xs text-gray-500">Complétées</p>
|
|
75
|
+
<p className="font-semibold text-gray-900">{completed}</p>
|
|
79
76
|
</div>
|
|
80
77
|
</div>
|
|
81
|
-
<div className="
|
|
82
|
-
<div className="
|
|
78
|
+
<div className="flex items-center gap-2 rounded-lg bg-blue-50 p-2">
|
|
79
|
+
<div className="h-3 w-3 rounded-full bg-blue-500" />
|
|
83
80
|
<div>
|
|
84
|
-
<p className="text-
|
|
85
|
-
<p className="
|
|
81
|
+
<p className="text-xs text-gray-500">En attente</p>
|
|
82
|
+
<p className="font-semibold text-gray-900">{pending}</p>
|
|
86
83
|
</div>
|
|
87
84
|
</div>
|
|
88
85
|
</div>
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import Link from 'next/link';
|
|
4
|
-
import { Calendar, Phone, Video, Mail, CheckCircle2, Clock
|
|
5
|
-
import { cn } from '@/lib/utils';
|
|
4
|
+
import { Calendar, Phone, Video, Mail, CheckCircle2, Clock } from 'lucide-react';
|
|
6
5
|
|
|
7
6
|
interface Task {
|
|
8
7
|
id: string;
|
|
@@ -14,26 +13,25 @@ interface Task {
|
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
interface UpcomingTasksListProps {
|
|
17
|
-
|
|
16
|
+
tasks: Task[];
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
const taskIcons
|
|
19
|
+
const taskIcons = {
|
|
21
20
|
CALL: Phone,
|
|
22
21
|
MEETING: Calendar,
|
|
23
22
|
EMAIL: Mail,
|
|
24
23
|
VIDEO_CONFERENCE: Video,
|
|
25
|
-
TASK: ListTodo,
|
|
26
24
|
OTHER: CheckCircle2,
|
|
27
25
|
};
|
|
28
26
|
|
|
29
|
-
const
|
|
30
|
-
LOW: '
|
|
31
|
-
MEDIUM: '
|
|
32
|
-
HIGH: '
|
|
33
|
-
URGENT: '
|
|
27
|
+
const priorityColors = {
|
|
28
|
+
LOW: 'text-gray-500 bg-gray-100',
|
|
29
|
+
MEDIUM: 'text-blue-600 bg-blue-100',
|
|
30
|
+
HIGH: 'text-orange-600 bg-orange-100',
|
|
31
|
+
URGENT: 'text-red-600 bg-red-100',
|
|
34
32
|
};
|
|
35
33
|
|
|
36
|
-
const priorityLabels
|
|
34
|
+
const priorityLabels = {
|
|
37
35
|
LOW: 'Basse',
|
|
38
36
|
MEDIUM: 'Moyenne',
|
|
39
37
|
HIGH: 'Haute',
|
|
@@ -41,79 +39,87 @@ const priorityLabels: Record<string, string> = {
|
|
|
41
39
|
};
|
|
42
40
|
|
|
43
41
|
export function UpcomingTasksList({ tasks }: UpcomingTasksListProps) {
|
|
42
|
+
if (tasks.length === 0) {
|
|
43
|
+
return (
|
|
44
|
+
<div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
|
|
45
|
+
<div className="flex items-center justify-between">
|
|
46
|
+
<h3 className="text-lg font-semibold text-gray-900">Tâches à Venir</h3>
|
|
47
|
+
<Link
|
|
48
|
+
href="/agenda"
|
|
49
|
+
className="text-sm font-medium text-blue-600 hover:text-blue-700"
|
|
50
|
+
>
|
|
51
|
+
Voir tout
|
|
52
|
+
</Link>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="mt-6 text-center text-sm text-gray-500">
|
|
55
|
+
<Clock className="mx-auto h-12 w-12 text-gray-400" />
|
|
56
|
+
<p className="mt-2">Aucune tâche à venir</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
return (
|
|
45
|
-
<div className="
|
|
63
|
+
<div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
|
46
64
|
<div className="mb-4 flex items-center justify-between">
|
|
47
|
-
<
|
|
48
|
-
<h3 className="text-base font-semibold text-gray-900">Tâches à Venir</h3>
|
|
49
|
-
<p className="mt-0.5 text-xs text-gray-400">Prochains rendez-vous et tâches</p>
|
|
50
|
-
</div>
|
|
65
|
+
<h3 className="text-lg font-bold text-gray-900">Liste des Tâches</h3>
|
|
51
66
|
<Link
|
|
52
67
|
href="/agenda"
|
|
53
|
-
className="
|
|
68
|
+
className="text-sm font-semibold text-blue-600 transition-colors hover:text-blue-700"
|
|
54
69
|
>
|
|
55
70
|
Voir tout →
|
|
56
71
|
</Link>
|
|
57
72
|
</div>
|
|
73
|
+
<div className="space-y-3">
|
|
74
|
+
{tasks.map((task) => {
|
|
75
|
+
const Icon = taskIcons[task.type as keyof typeof taskIcons] || CheckCircle2;
|
|
76
|
+
const scheduledDate = new Date(task.scheduledAt);
|
|
58
77
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<span
|
|
84
|
-
className={cn(
|
|
85
|
-
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium',
|
|
86
|
-
priorityStyles[task.priority] || 'bg-gray-100 text-gray-600',
|
|
87
|
-
)}
|
|
88
|
-
>
|
|
89
|
-
{priorityLabels[task.priority] || task.priority}
|
|
90
|
-
</span>
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
key={task.id}
|
|
81
|
+
className="group flex items-start gap-3 rounded-lg border border-gray-100 bg-gray-50/50 p-3 transition-all duration-200 hover:border-blue-200 hover:bg-blue-50/30"
|
|
82
|
+
>
|
|
83
|
+
<input
|
|
84
|
+
type="checkbox"
|
|
85
|
+
className="mt-1 h-4 w-4 cursor-pointer rounded border-gray-300 text-blue-600 focus:ring-2 focus:ring-gray-400/30"
|
|
86
|
+
/>
|
|
87
|
+
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-linear-to-br from-blue-100 to-purple-100">
|
|
88
|
+
<Icon className="h-4 w-4 text-blue-600" />
|
|
89
|
+
</div>
|
|
90
|
+
<div className="flex-1">
|
|
91
|
+
<div className="flex items-start justify-between">
|
|
92
|
+
<div>
|
|
93
|
+
<p className="font-semibold text-gray-900">{task.title}</p>
|
|
94
|
+
{task.contact && (
|
|
95
|
+
<Link
|
|
96
|
+
href={`/contacts/${task.contact.id}`}
|
|
97
|
+
className="text-sm text-gray-600 transition-colors hover:text-blue-600"
|
|
98
|
+
>
|
|
99
|
+
{task.contact.name}
|
|
100
|
+
</Link>
|
|
101
|
+
)}
|
|
91
102
|
</div>
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
className="dash-hover-text cursor-pointer text-xs text-gray-500 transition-colors"
|
|
96
|
-
>
|
|
97
|
-
{task.contact.name}
|
|
98
|
-
</Link>
|
|
99
|
-
)}
|
|
100
|
-
<p className="mt-1 text-[11px] text-gray-400">
|
|
101
|
-
{scheduledDate.toLocaleDateString('fr-FR', {
|
|
102
|
-
day: 'numeric',
|
|
103
|
-
month: 'long',
|
|
104
|
-
})}{' '}
|
|
105
|
-
à{' '}
|
|
106
|
-
{scheduledDate.toLocaleTimeString('fr-FR', {
|
|
107
|
-
hour: '2-digit',
|
|
108
|
-
minute: '2-digit',
|
|
109
|
-
})}
|
|
110
|
-
</p>
|
|
103
|
+
<button className="cursor-pointer opacity-0 transition-opacity group-hover:opacity-100 hover:opacity-100">
|
|
104
|
+
<span className="text-gray-400">⋯</span>
|
|
105
|
+
</button>
|
|
111
106
|
</div>
|
|
107
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
108
|
+
{scheduledDate.toLocaleDateString('fr-FR', {
|
|
109
|
+
day: 'numeric',
|
|
110
|
+
month: 'long',
|
|
111
|
+
})}{' '}
|
|
112
|
+
à{' '}
|
|
113
|
+
{scheduledDate.toLocaleTimeString('fr-FR', {
|
|
114
|
+
hour: '2-digit',
|
|
115
|
+
minute: '2-digit',
|
|
116
|
+
})}
|
|
117
|
+
</p>
|
|
112
118
|
</div>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
})}
|
|
122
|
+
</div>
|
|
117
123
|
</div>
|
|
118
124
|
);
|
|
119
125
|
}
|