create-crm-tmp 1.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/bin/create-crm-tmp.js +93 -0
- package/package.json +25 -0
- package/template/.prettierignore +33 -0
- package/template/.prettierrc.json +25 -0
- package/template/README.md +173 -0
- package/template/eslint.config.mjs +18 -0
- package/template/exemple-contacts.csv +11 -0
- package/template/next.config.ts +8 -0
- package/template/package.json +64 -0
- package/template/postcss.config.mjs +7 -0
- package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
- package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
- package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
- package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
- package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
- package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
- package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
- package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
- package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
- package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
- package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
- package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
- package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
- package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
- package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
- package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
- package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
- package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
- package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
- package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
- package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
- package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
- package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
- package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
- package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
- package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
- package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
- package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
- package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
- package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
- package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
- package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
- package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
- package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
- package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +582 -0
- package/template/prisma.config.ts +14 -0
- package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
- package/template/src/app/(auth)/layout.tsx +3 -0
- package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
- package/template/src/app/(auth)/reset-password/page.tsx +146 -0
- package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
- package/template/src/app/(auth)/signin/page.tsx +166 -0
- package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
- package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
- package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
- package/template/src/app/(dashboard)/layout.tsx +30 -0
- package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
- package/template/src/app/(dashboard)/templates/page.tsx +567 -0
- package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
- package/template/src/app/(dashboard)/users/page.tsx +457 -0
- package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
- package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
- package/template/src/app/api/audit-logs/route.ts +57 -0
- package/template/src/app/api/auth/[...all]/route.ts +4 -0
- package/template/src/app/api/auth/check-active/route.ts +31 -0
- package/template/src/app/api/auth/google/callback/route.ts +94 -0
- package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
- package/template/src/app/api/auth/google/route.ts +34 -0
- package/template/src/app/api/auth/google/status/route.ts +32 -0
- package/template/src/app/api/closing-reasons/route.ts +27 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
- package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
- package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
- package/template/src/app/api/contacts/[id]/route.ts +322 -0
- package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
- package/template/src/app/api/contacts/export/route.ts +270 -0
- package/template/src/app/api/contacts/import/route.ts +381 -0
- package/template/src/app/api/contacts/route.ts +283 -0
- package/template/src/app/api/dashboard/stats/route.ts +299 -0
- package/template/src/app/api/email/track/[id]/route.ts +68 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
- package/template/src/app/api/invite/complete/route.ts +88 -0
- package/template/src/app/api/invite/validate/route.ts +55 -0
- package/template/src/app/api/reminders/route.ts +95 -0
- package/template/src/app/api/reset-password/complete/route.ts +73 -0
- package/template/src/app/api/reset-password/request/route.ts +84 -0
- package/template/src/app/api/reset-password/validate/route.ts +49 -0
- package/template/src/app/api/reset-password/verify/route.ts +74 -0
- package/template/src/app/api/roles/[id]/route.ts +183 -0
- package/template/src/app/api/roles/route.ts +140 -0
- package/template/src/app/api/send/route.ts +282 -0
- package/template/src/app/api/settings/change-password/route.ts +95 -0
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
- package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
- package/template/src/app/api/settings/company/route.ts +121 -0
- package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
- package/template/src/app/api/settings/google-ads/route.ts +122 -0
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
- package/template/src/app/api/settings/google-sheet/route.ts +254 -0
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
- package/template/src/app/api/settings/meta-leads/route.ts +132 -0
- package/template/src/app/api/settings/profile/route.ts +42 -0
- package/template/src/app/api/settings/smtp/route.ts +130 -0
- package/template/src/app/api/settings/smtp/test/route.ts +121 -0
- package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
- package/template/src/app/api/settings/statuses/route.ts +83 -0
- package/template/src/app/api/statuses/route.ts +25 -0
- package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
- package/template/src/app/api/tasks/[id]/route.ts +728 -0
- package/template/src/app/api/tasks/meet/route.ts +240 -0
- package/template/src/app/api/tasks/route.ts +417 -0
- package/template/src/app/api/templates/[id]/route.ts +140 -0
- package/template/src/app/api/templates/route.ts +91 -0
- package/template/src/app/api/users/[id]/route.ts +168 -0
- package/template/src/app/api/users/list/route.ts +45 -0
- package/template/src/app/api/users/me/route.ts +48 -0
- package/template/src/app/api/users/route.ts +250 -0
- package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
- package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
- package/template/src/app/api/workflows/[id]/route.ts +192 -0
- package/template/src/app/api/workflows/process/route.ts +293 -0
- package/template/src/app/api/workflows/route.ts +124 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +1416 -0
- package/template/src/app/layout.tsx +31 -0
- package/template/src/app/page.tsx +32 -0
- package/template/src/components/dashboard/activity-chart.tsx +67 -0
- package/template/src/components/dashboard/contacts-chart.tsx +63 -0
- package/template/src/components/dashboard/recent-activity.tsx +164 -0
- package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
- package/template/src/components/dashboard/stat-card.tsx +61 -0
- package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
- package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
- package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
- package/template/src/components/editor.tsx +856 -0
- package/template/src/components/email-template.tsx +35 -0
- package/template/src/components/header.tsx +320 -0
- package/template/src/components/invitation-email-template.tsx +79 -0
- package/template/src/components/meet-cancellation-email-template.tsx +120 -0
- package/template/src/components/meet-confirmation-email-template.tsx +156 -0
- package/template/src/components/meet-update-email-template.tsx +209 -0
- package/template/src/components/page-header.tsx +61 -0
- package/template/src/components/reset-password-email-template.tsx +79 -0
- package/template/src/components/sidebar.tsx +294 -0
- package/template/src/components/skeleton.tsx +380 -0
- package/template/src/components/ui/commands.tsx +396 -0
- package/template/src/components/ui/components.tsx +150 -0
- package/template/src/components/ui/theme.tsx +5 -0
- package/template/src/components/view-as-banner.tsx +45 -0
- package/template/src/components/view-as-modal.tsx +186 -0
- package/template/src/contexts/mobile-menu-context.tsx +31 -0
- package/template/src/contexts/sidebar-context.tsx +107 -0
- package/template/src/contexts/task-reminder-context.tsx +239 -0
- package/template/src/contexts/view-as-context.tsx +84 -0
- package/template/src/hooks/use-user-role.ts +82 -0
- package/template/src/lib/audit-log.ts +45 -0
- package/template/src/lib/auth-client.ts +16 -0
- package/template/src/lib/auth.ts +35 -0
- package/template/src/lib/check-permission.ts +193 -0
- package/template/src/lib/contact-duplicate.ts +112 -0
- package/template/src/lib/contact-interactions.ts +371 -0
- package/template/src/lib/encryption.ts +99 -0
- package/template/src/lib/google-calendar.ts +300 -0
- package/template/src/lib/google-drive.ts +372 -0
- package/template/src/lib/permissions.ts +412 -0
- package/template/src/lib/prisma.ts +32 -0
- package/template/src/lib/roles.ts +120 -0
- package/template/src/lib/template-variables.ts +76 -0
- package/template/src/lib/utils.ts +46 -0
- package/template/src/lib/workflow-executor.ts +482 -0
- package/template/src/proxy.ts +91 -0
- package/template/tsconfig.json +34 -0
- package/template/vercel.json +8 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { Geist, Geist_Mono } from 'next/font/google';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
const geistSans = Geist({
|
|
7
|
+
variable: '--font-geist-sans',
|
|
8
|
+
subsets: ['latin'],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const geistMono = Geist_Mono({
|
|
12
|
+
variable: '--font-geist-mono',
|
|
13
|
+
subsets: ['latin'],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const metadata: Metadata = {
|
|
17
|
+
title: 'Create Next App',
|
|
18
|
+
description: 'Generated by create next app',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default function RootLayout({
|
|
22
|
+
children,
|
|
23
|
+
}: Readonly<{
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}>) {
|
|
26
|
+
return (
|
|
27
|
+
<html lang="fr">
|
|
28
|
+
<body className={cn(geistSans.variable, geistMono.variable, 'antialiased')}>{children}</body>
|
|
29
|
+
</html>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSession } from '@/lib/auth-client';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
export default function HomePage() {
|
|
8
|
+
const { data: session, isPending } = useSession();
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (isPending) return;
|
|
13
|
+
|
|
14
|
+
if (session) {
|
|
15
|
+
// Utilisateur connecté -> rediriger vers le dashboard
|
|
16
|
+
router.push('/dashboard');
|
|
17
|
+
} else {
|
|
18
|
+
// Utilisateur non connecté -> rediriger vers la page de connexion
|
|
19
|
+
router.push('/signin');
|
|
20
|
+
}
|
|
21
|
+
}, [session, isPending, router]);
|
|
22
|
+
|
|
23
|
+
// Afficher un loader pendant la redirection
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex min-h-screen items-center justify-center bg-gray-50">
|
|
26
|
+
<div className="flex flex-col items-center gap-4">
|
|
27
|
+
<div className="h-12 w-12 animate-spin rounded-full border-4 border-gray-200 border-t-indigo-600"></div>
|
|
28
|
+
<p className="text-sm text-gray-600">Redirection...</p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis, Legend } from 'recharts';
|
|
4
|
+
|
|
5
|
+
interface ActivityChartProps {
|
|
6
|
+
data: Array<{ date: string; interactions: number; tasks: number }>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ActivityChart({ data }: ActivityChartProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
|
12
|
+
<div className="mb-4">
|
|
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>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="h-[300px]">
|
|
17
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
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>
|
|
29
|
+
<XAxis
|
|
30
|
+
dataKey="date"
|
|
31
|
+
stroke="#9ca3af"
|
|
32
|
+
fontSize={12}
|
|
33
|
+
tickLine={false}
|
|
34
|
+
axisLine={false}
|
|
35
|
+
/>
|
|
36
|
+
<YAxis stroke="#9ca3af" fontSize={12} tickLine={false} axisLine={false} />
|
|
37
|
+
<Tooltip
|
|
38
|
+
contentStyle={{
|
|
39
|
+
backgroundColor: 'white',
|
|
40
|
+
border: '1px solid #e5e7eb',
|
|
41
|
+
borderRadius: '12px',
|
|
42
|
+
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
43
|
+
}}
|
|
44
|
+
labelStyle={{ color: '#374151', fontWeight: 600 }}
|
|
45
|
+
/>
|
|
46
|
+
<Legend
|
|
47
|
+
wrapperStyle={{ paddingTop: '20px' }}
|
|
48
|
+
iconType="circle"
|
|
49
|
+
/>
|
|
50
|
+
<Bar
|
|
51
|
+
dataKey="interactions"
|
|
52
|
+
fill="url(#interactionsGradient)"
|
|
53
|
+
radius={[8, 8, 0, 0]}
|
|
54
|
+
name="Interactions"
|
|
55
|
+
/>
|
|
56
|
+
<Bar
|
|
57
|
+
dataKey="tasks"
|
|
58
|
+
fill="url(#tasksGradient)"
|
|
59
|
+
radius={[8, 8, 0, 0]}
|
|
60
|
+
name="Tâches"
|
|
61
|
+
/>
|
|
62
|
+
</BarChart>
|
|
63
|
+
</ResponsiveContainer>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
|
4
|
+
|
|
5
|
+
interface ContactsChartProps {
|
|
6
|
+
data: Array<{ month: string; count: number }>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ContactsChart({ data }: ContactsChartProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
|
12
|
+
<div className="mb-4 flex items-center justify-between">
|
|
13
|
+
<div>
|
|
14
|
+
<h3 className="text-lg font-bold text-gray-900">Évolution des Contacts</h3>
|
|
15
|
+
<p className="mt-1 text-sm text-gray-500">Nombre de contacts créés par mois</p>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div className="h-[300px]">
|
|
19
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
20
|
+
<AreaChart data={data}>
|
|
21
|
+
<defs>
|
|
22
|
+
<linearGradient id="colorCount" x1="0" y1="0" x2="0" y2="1">
|
|
23
|
+
<stop offset="5%" stopColor="#8b5cf6" stopOpacity={0.4} />
|
|
24
|
+
<stop offset="50%" stopColor="#6366f1" stopOpacity={0.3} />
|
|
25
|
+
<stop offset="95%" stopColor="#6366f1" stopOpacity={0} />
|
|
26
|
+
</linearGradient>
|
|
27
|
+
</defs>
|
|
28
|
+
<XAxis
|
|
29
|
+
dataKey="month"
|
|
30
|
+
stroke="#9ca3af"
|
|
31
|
+
fontSize={12}
|
|
32
|
+
tickLine={false}
|
|
33
|
+
axisLine={false}
|
|
34
|
+
/>
|
|
35
|
+
<YAxis
|
|
36
|
+
stroke="#9ca3af"
|
|
37
|
+
fontSize={12}
|
|
38
|
+
tickLine={false}
|
|
39
|
+
axisLine={false}
|
|
40
|
+
tickFormatter={(value) => `${value}`}
|
|
41
|
+
/>
|
|
42
|
+
<Tooltip
|
|
43
|
+
contentStyle={{
|
|
44
|
+
backgroundColor: 'white',
|
|
45
|
+
border: '1px solid #e5e7eb',
|
|
46
|
+
borderRadius: '12px',
|
|
47
|
+
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
48
|
+
}}
|
|
49
|
+
labelStyle={{ color: '#374151', fontWeight: 600 }}
|
|
50
|
+
/>
|
|
51
|
+
<Area
|
|
52
|
+
type="monotone"
|
|
53
|
+
dataKey="count"
|
|
54
|
+
stroke="#8b5cf6"
|
|
55
|
+
strokeWidth={3}
|
|
56
|
+
fill="url(#colorCount)"
|
|
57
|
+
/>
|
|
58
|
+
</AreaChart>
|
|
59
|
+
</ResponsiveContainer>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import {
|
|
5
|
+
Phone,
|
|
6
|
+
Mail,
|
|
7
|
+
Calendar,
|
|
8
|
+
MessageSquare,
|
|
9
|
+
FileText,
|
|
10
|
+
TrendingUp,
|
|
11
|
+
RefreshCw,
|
|
12
|
+
CalendarCheck,
|
|
13
|
+
CalendarX,
|
|
14
|
+
CalendarClock,
|
|
15
|
+
UserCheck,
|
|
16
|
+
} from 'lucide-react';
|
|
17
|
+
import { cn } from '@/lib/utils';
|
|
18
|
+
|
|
19
|
+
interface Interaction {
|
|
20
|
+
id: string;
|
|
21
|
+
type: string;
|
|
22
|
+
title: string | null;
|
|
23
|
+
content: string;
|
|
24
|
+
date: string;
|
|
25
|
+
contact: {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface RecentActivityProps {
|
|
32
|
+
interactions: Interaction[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const interactionIcons = {
|
|
36
|
+
CALL: Phone,
|
|
37
|
+
SMS: MessageSquare,
|
|
38
|
+
EMAIL: Mail,
|
|
39
|
+
MEETING: Calendar,
|
|
40
|
+
NOTE: FileText,
|
|
41
|
+
STATUS_CHANGE: TrendingUp,
|
|
42
|
+
CONTACT_UPDATE: RefreshCw,
|
|
43
|
+
APPOINTMENT_CREATED: CalendarCheck,
|
|
44
|
+
APPOINTMENT_DELETED: CalendarX,
|
|
45
|
+
APPOINTMENT_CHANGED: CalendarClock,
|
|
46
|
+
ASSIGNMENT_CHANGE: UserCheck,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const interactionColors = {
|
|
50
|
+
CALL: 'bg-blue-100 text-blue-600',
|
|
51
|
+
SMS: 'bg-green-100 text-green-600',
|
|
52
|
+
EMAIL: 'bg-purple-100 text-purple-600',
|
|
53
|
+
MEETING: 'bg-indigo-100 text-indigo-600',
|
|
54
|
+
NOTE: 'bg-gray-100 text-gray-600',
|
|
55
|
+
STATUS_CHANGE: 'bg-amber-100 text-amber-600',
|
|
56
|
+
CONTACT_UPDATE: 'bg-cyan-100 text-cyan-600',
|
|
57
|
+
APPOINTMENT_CREATED: 'bg-emerald-100 text-emerald-600',
|
|
58
|
+
APPOINTMENT_DELETED: 'bg-red-100 text-red-600',
|
|
59
|
+
APPOINTMENT_CHANGED: 'bg-orange-100 text-orange-600',
|
|
60
|
+
ASSIGNMENT_CHANGE: 'bg-pink-100 text-pink-600',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const interactionLabels = {
|
|
64
|
+
CALL: 'Appel',
|
|
65
|
+
SMS: 'SMS',
|
|
66
|
+
EMAIL: 'Email',
|
|
67
|
+
MEETING: 'Réunion',
|
|
68
|
+
NOTE: 'Note',
|
|
69
|
+
STATUS_CHANGE: 'Changement de statut',
|
|
70
|
+
CONTACT_UPDATE: 'Mise à jour',
|
|
71
|
+
APPOINTMENT_CREATED: 'RDV créé',
|
|
72
|
+
APPOINTMENT_DELETED: 'RDV supprimé',
|
|
73
|
+
APPOINTMENT_CHANGED: 'RDV modifié',
|
|
74
|
+
ASSIGNMENT_CHANGE: 'Assignation',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export function RecentActivity({ interactions }: RecentActivityProps) {
|
|
78
|
+
if (interactions.length === 0) {
|
|
79
|
+
return (
|
|
80
|
+
<div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
|
|
81
|
+
<div className="flex items-center justify-between">
|
|
82
|
+
<h3 className="text-lg font-semibold text-gray-900">Activité Récente</h3>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="mt-6 text-center text-sm text-gray-500">
|
|
85
|
+
<FileText className="mx-auto h-12 w-12 text-gray-400" />
|
|
86
|
+
<p className="mt-2">Aucune activité récente</p>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
|
94
|
+
<div className="mb-4 flex items-center justify-between">
|
|
95
|
+
<h3 className="text-lg font-bold text-gray-900">Activité Récente</h3>
|
|
96
|
+
<Link
|
|
97
|
+
href="/contacts"
|
|
98
|
+
className="text-sm font-semibold text-indigo-600 transition-colors hover:text-indigo-700"
|
|
99
|
+
>
|
|
100
|
+
Voir tout →
|
|
101
|
+
</Link>
|
|
102
|
+
</div>
|
|
103
|
+
<div className="space-y-3">
|
|
104
|
+
{interactions.map((interaction) => {
|
|
105
|
+
const Icon =
|
|
106
|
+
interactionIcons[interaction.type as keyof typeof interactionIcons] || FileText;
|
|
107
|
+
const color =
|
|
108
|
+
interactionColors[interaction.type as keyof typeof interactionColors] ||
|
|
109
|
+
'bg-gray-100 text-gray-600';
|
|
110
|
+
const label =
|
|
111
|
+
interactionLabels[interaction.type as keyof typeof interactionLabels] ||
|
|
112
|
+
interaction.type;
|
|
113
|
+
const date = new Date(interaction.date);
|
|
114
|
+
const now = new Date();
|
|
115
|
+
const diffMs = now.getTime() - date.getTime();
|
|
116
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
117
|
+
const diffMinutes = Math.floor(diffMs / (1000 * 60));
|
|
118
|
+
|
|
119
|
+
let timeAgo = '';
|
|
120
|
+
if (diffMinutes < 1) {
|
|
121
|
+
timeAgo = "À l'instant";
|
|
122
|
+
} else if (diffMinutes < 60) {
|
|
123
|
+
timeAgo = `Il y a ${diffMinutes} min`;
|
|
124
|
+
} else if (diffHours < 24) {
|
|
125
|
+
timeAgo = `Il y a ${diffHours}h`;
|
|
126
|
+
} else {
|
|
127
|
+
timeAgo = date.toLocaleDateString('fr-FR', {
|
|
128
|
+
day: 'numeric',
|
|
129
|
+
month: 'short',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
key={interaction.id}
|
|
136
|
+
className="group flex items-start gap-3 rounded-lg border-l-4 border-transparent bg-gray-50/50 p-3 transition-all duration-200 hover:border-indigo-400 hover:bg-indigo-50/30"
|
|
137
|
+
>
|
|
138
|
+
<div className={cn('rounded-lg p-2 shadow-sm', color)}>
|
|
139
|
+
<Icon className="h-4 w-4" />
|
|
140
|
+
</div>
|
|
141
|
+
<div className="flex-1">
|
|
142
|
+
<div className="flex items-start justify-between">
|
|
143
|
+
<div>
|
|
144
|
+
<p className="text-sm font-semibold text-gray-900">{label}</p>
|
|
145
|
+
<Link
|
|
146
|
+
href={`/contacts/${interaction.contact.id}`}
|
|
147
|
+
className="text-sm text-gray-600 transition-colors hover:text-indigo-600"
|
|
148
|
+
>
|
|
149
|
+
{interaction.contact.name}
|
|
150
|
+
</Link>
|
|
151
|
+
{interaction.title && (
|
|
152
|
+
<p className="mt-1 text-xs text-gray-500">{interaction.title}</p>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
<span className="text-xs font-medium text-gray-500">{timeAgo}</span>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
|
4
|
+
|
|
5
|
+
interface SalesAnalyticsChartProps {
|
|
6
|
+
data: Array<{ month: string; count: number }>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function SalesAnalyticsChart({ data }: SalesAnalyticsChartProps) {
|
|
10
|
+
// Calculer le total et la croissance
|
|
11
|
+
const total = data.reduce((sum, item) => sum + item.count, 0);
|
|
12
|
+
const previousTotal = data.length > 1 ? data.slice(0, -1).reduce((sum, item) => sum + item.count, 0) : 0;
|
|
13
|
+
const growth = previousTotal > 0 ? ((total - previousTotal) / previousTotal) * 100 : 0;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
|
17
|
+
<div className="mb-4 flex items-center justify-between">
|
|
18
|
+
<div>
|
|
19
|
+
<h3 className="text-lg font-bold text-gray-900">Analytiques des Ventes</h3>
|
|
20
|
+
</div>
|
|
21
|
+
<select className="rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
|
22
|
+
<option>Mensuel</option>
|
|
23
|
+
<option>Hebdomadaire</option>
|
|
24
|
+
<option>Annuel</option>
|
|
25
|
+
</select>
|
|
26
|
+
</div>
|
|
27
|
+
<div className="mb-4 flex items-baseline gap-4">
|
|
28
|
+
<div>
|
|
29
|
+
<p className="text-2xl font-bold text-gray-900">
|
|
30
|
+
{total.toLocaleString('fr-FR')} contacts ce mois
|
|
31
|
+
</p>
|
|
32
|
+
<p className="mt-1 text-sm font-semibold text-emerald-600">
|
|
33
|
+
Augmentation ce mois : +{growth.toFixed(1)}%
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="h-[280px]">
|
|
38
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
39
|
+
<BarChart data={data} barCategoryGap="20%">
|
|
40
|
+
<defs>
|
|
41
|
+
<linearGradient id="barGradient" x1="0" y1="0" x2="0" y2="1">
|
|
42
|
+
<stop offset="0%" stopColor="#8b5cf6" stopOpacity={1} />
|
|
43
|
+
<stop offset="100%" stopColor="#6366f1" stopOpacity={0.8} />
|
|
44
|
+
</linearGradient>
|
|
45
|
+
</defs>
|
|
46
|
+
<XAxis
|
|
47
|
+
dataKey="month"
|
|
48
|
+
stroke="#9ca3af"
|
|
49
|
+
fontSize={12}
|
|
50
|
+
tickLine={false}
|
|
51
|
+
axisLine={false}
|
|
52
|
+
/>
|
|
53
|
+
<YAxis
|
|
54
|
+
stroke="#9ca3af"
|
|
55
|
+
fontSize={12}
|
|
56
|
+
tickLine={false}
|
|
57
|
+
axisLine={false}
|
|
58
|
+
tickFormatter={(value) => `${value}`}
|
|
59
|
+
/>
|
|
60
|
+
<Tooltip
|
|
61
|
+
contentStyle={{
|
|
62
|
+
backgroundColor: 'white',
|
|
63
|
+
border: '1px solid #e5e7eb',
|
|
64
|
+
borderRadius: '12px',
|
|
65
|
+
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
66
|
+
}}
|
|
67
|
+
labelStyle={{ color: '#374151', fontWeight: 600 }}
|
|
68
|
+
formatter={(value: number) => [`${value.toLocaleString('fr-FR')}`, 'Contacts']}
|
|
69
|
+
/>
|
|
70
|
+
<Bar
|
|
71
|
+
dataKey="count"
|
|
72
|
+
fill="url(#barGradient)"
|
|
73
|
+
radius={[8, 8, 0, 0]}
|
|
74
|
+
/>
|
|
75
|
+
</BarChart>
|
|
76
|
+
</ResponsiveContainer>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { LucideIcon } from 'lucide-react';
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
interface StatCardProps {
|
|
7
|
+
title: string;
|
|
8
|
+
value: string | number;
|
|
9
|
+
icon: LucideIcon;
|
|
10
|
+
trend?: {
|
|
11
|
+
value: number;
|
|
12
|
+
label: string;
|
|
13
|
+
};
|
|
14
|
+
iconColor?: string;
|
|
15
|
+
iconBgColor?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function StatCard({
|
|
19
|
+
title,
|
|
20
|
+
value,
|
|
21
|
+
icon: Icon,
|
|
22
|
+
trend,
|
|
23
|
+
iconColor = 'text-indigo-600',
|
|
24
|
+
iconBgColor = 'bg-indigo-100',
|
|
25
|
+
}: StatCardProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="group relative overflow-hidden rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-all duration-300 hover:shadow-xl hover:-translate-y-1">
|
|
28
|
+
<div className="absolute inset-0 bg-gradient-to-br from-white via-purple-50/30 to-indigo-50/30 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
|
29
|
+
<div className="relative flex items-center justify-between">
|
|
30
|
+
<div className="flex-1">
|
|
31
|
+
<p className="text-sm font-medium text-gray-600">{title}</p>
|
|
32
|
+
<p className="mt-2 bg-gradient-to-r from-gray-900 to-gray-700 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-gray-500">{trend.label}</span>
|
|
47
|
+
</p>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
<div
|
|
51
|
+
className={cn(
|
|
52
|
+
'rounded-xl bg-gradient-to-br p-4 shadow-md transition-transform duration-300 group-hover:scale-110 group-hover:shadow-lg',
|
|
53
|
+
iconBgColor,
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
<Icon className={cn('h-6 w-6', iconColor)} />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { PolarAngleAxis, PolarGrid, Radar, RadarChart, ResponsiveContainer } from 'recharts';
|
|
4
|
+
|
|
5
|
+
interface StatusDistributionChartProps {
|
|
6
|
+
data: Array<{ name: string; value: number }>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function StatusDistributionChart({ data }: StatusDistributionChartProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="rounded-xl border border-gray-200/50 bg-white p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
|
12
|
+
<div className="mb-4">
|
|
13
|
+
<h3 className="text-lg font-bold text-gray-900">Répartition par Statut</h3>
|
|
14
|
+
<p className="mt-1 text-sm text-gray-500">Distribution des contacts selon leur statut</p>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="h-[300px]">
|
|
17
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
18
|
+
<RadarChart data={data}>
|
|
19
|
+
<defs>
|
|
20
|
+
<linearGradient id="radarGradient" x1="0" y1="0" x2="0" y2="1">
|
|
21
|
+
<stop offset="0%" stopColor="#8b5cf6" stopOpacity={0.8} />
|
|
22
|
+
<stop offset="100%" stopColor="#6366f1" stopOpacity={0.4} />
|
|
23
|
+
</linearGradient>
|
|
24
|
+
</defs>
|
|
25
|
+
<PolarGrid stroke="#e5e7eb" />
|
|
26
|
+
<PolarAngleAxis
|
|
27
|
+
dataKey="name"
|
|
28
|
+
stroke="#6b7280"
|
|
29
|
+
fontSize={12}
|
|
30
|
+
tick={{ fill: '#6b7280' }}
|
|
31
|
+
/>
|
|
32
|
+
<Radar
|
|
33
|
+
name="Contacts"
|
|
34
|
+
dataKey="value"
|
|
35
|
+
stroke="#8b5cf6"
|
|
36
|
+
fill="url(#radarGradient)"
|
|
37
|
+
fillOpacity={0.6}
|
|
38
|
+
strokeWidth={2}
|
|
39
|
+
/>
|
|
40
|
+
</RadarChart>
|
|
41
|
+
</ResponsiveContainer>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
|
|
4
|
+
|
|
5
|
+
interface TasksPieChartProps {
|
|
6
|
+
completed: number;
|
|
7
|
+
pending: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const COLORS = {
|
|
11
|
+
completed: '#10b981',
|
|
12
|
+
pending: '#f59e0b',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function TasksPieChart({ completed, pending }: TasksPieChartProps) {
|
|
16
|
+
const data = [
|
|
17
|
+
{ name: 'Complétées', value: completed },
|
|
18
|
+
{ name: 'En attente', value: pending },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const total = completed + pending;
|
|
22
|
+
const completionRate = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
23
|
+
|
|
24
|
+
return (
|
|
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>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="flex items-center justify-center">
|
|
31
|
+
<div className="relative h-[200px] w-[200px]">
|
|
32
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
33
|
+
<PieChart>
|
|
34
|
+
<Pie
|
|
35
|
+
data={data}
|
|
36
|
+
cx="50%"
|
|
37
|
+
cy="50%"
|
|
38
|
+
innerRadius={60}
|
|
39
|
+
outerRadius={80}
|
|
40
|
+
paddingAngle={2}
|
|
41
|
+
dataKey="value"
|
|
42
|
+
>
|
|
43
|
+
{data.map((entry, index) => (
|
|
44
|
+
<Cell
|
|
45
|
+
key={`cell-${index}`}
|
|
46
|
+
fill={entry.name === 'Complétées' ? COLORS.completed : COLORS.pending}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
</Pie>
|
|
50
|
+
<Tooltip
|
|
51
|
+
contentStyle={{
|
|
52
|
+
backgroundColor: 'white',
|
|
53
|
+
border: '1px solid #e5e7eb',
|
|
54
|
+
borderRadius: '12px',
|
|
55
|
+
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
</PieChart>
|
|
59
|
+
</ResponsiveContainer>
|
|
60
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
61
|
+
<div className="text-center">
|
|
62
|
+
<p className="text-3xl font-bold bg-gradient-to-r from-gray-900 to-gray-700 bg-clip-text text-transparent">
|
|
63
|
+
{completionRate}%
|
|
64
|
+
</p>
|
|
65
|
+
<p className="text-xs text-gray-500">Complétées</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
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" />
|
|
73
|
+
<div>
|
|
74
|
+
<p className="text-xs text-gray-500">Complétées</p>
|
|
75
|
+
<p className="font-semibold text-gray-900">{completed}</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="flex items-center gap-2 rounded-lg bg-amber-50 p-2">
|
|
79
|
+
<div className="h-3 w-3 rounded-full bg-amber-500" />
|
|
80
|
+
<div>
|
|
81
|
+
<p className="text-xs text-gray-500">En attente</p>
|
|
82
|
+
<p className="font-semibold text-gray-900">{pending}</p>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|