create-crm-tmp 1.0.2 → 1.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 +7 -3
- package/package.json +1 -1
- package/template/README.md +70 -5
- package/template/WORKFLOWS_CRON.md +49 -27
- package/template/package.json +18 -16
- package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +20 -0
- package/template/prisma/schema.prisma +17 -0
- package/template/src/app/(dashboard)/agenda/page.tsx +279 -225
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +1 -5
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +20 -47
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +0 -2
- package/template/src/app/(dashboard)/closing/page.tsx +5 -57
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +60 -44
- package/template/src/app/(dashboard)/contacts/page.tsx +156 -210
- package/template/src/app/(dashboard)/dashboard/page.tsx +438 -91
- package/template/src/app/(dashboard)/settings/page.tsx +179 -77
- package/template/src/app/(dashboard)/users/layout.tsx +30 -0
- package/template/src/app/(dashboard)/users/list/page.tsx +213 -159
- package/template/src/app/(dashboard)/users/page.tsx +13 -46
- package/template/src/app/(dashboard)/users/permissions/page.tsx +0 -2
- package/template/src/app/(dashboard)/users/roles/page.tsx +0 -2
- package/template/src/app/api/audit-logs/route.ts +0 -2
- package/template/src/app/api/auth/google/status/route.ts +46 -7
- package/template/src/app/api/closing-reasons/route.ts +0 -2
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +2 -1
- package/template/src/app/api/contacts/[id]/files/route.ts +25 -20
- package/template/src/app/api/contacts/[id]/route.ts +2 -3
- package/template/src/app/api/contacts/export/route.ts +14 -11
- package/template/src/app/api/contacts/import/route.ts +2 -6
- package/template/src/app/api/contacts/route.ts +1 -1
- package/template/src/app/api/dashboard/stats/route.ts +7 -0
- package/template/src/app/api/dashboard/widgets/[id]/route.ts +47 -0
- package/template/src/app/api/dashboard/widgets/route.ts +181 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +58 -28
- package/template/src/app/api/reminders/route.ts +4 -2
- package/template/src/app/api/roles/route.ts +1 -1
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +1 -6
- package/template/src/app/api/settings/closing-reasons/route.ts +0 -2
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +10 -5
- package/template/src/app/api/settings/google-sheet/route.ts +3 -3
- package/template/src/app/api/tasks/[id]/route.ts +4 -4
- package/template/src/app/api/tasks/meet/route.ts +1 -2
- package/template/src/app/api/tasks/route.ts +16 -18
- package/template/src/app/api/users/for-agenda/route.ts +1 -2
- package/template/src/app/api/workflows/[id]/route.ts +2 -9
- package/template/src/app/api/workflows/route.ts +0 -1
- package/template/src/app/globals.css +96 -0
- package/template/src/components/dashboard/activity-chart.tsx +37 -37
- package/template/src/components/dashboard/add-widget-dialog.tsx +161 -0
- package/template/src/components/dashboard/color-picker.tsx +65 -0
- package/template/src/components/dashboard/contacts-chart.tsx +36 -30
- package/template/src/components/dashboard/interactions-by-type-chart.tsx +121 -0
- package/template/src/components/dashboard/recent-activity.tsx +79 -86
- package/template/src/components/dashboard/sales-analytics-chart.tsx +4 -8
- package/template/src/components/dashboard/stat-card.tsx +42 -40
- package/template/src/components/dashboard/status-distribution-chart.tsx +64 -27
- package/template/src/components/dashboard/tasks-pie-chart.tsx +37 -34
- package/template/src/components/dashboard/top-contacts-list.tsx +41 -51
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +71 -78
- package/template/src/components/dashboard/widget-wrapper.tsx +39 -0
- package/template/src/components/header.tsx +21 -12
- package/template/src/components/page-header.tsx +14 -47
- package/template/src/components/sidebar.tsx +3 -4
- package/template/src/contexts/dashboard-theme-context.tsx +58 -0
- package/template/src/lib/audit-log.ts +0 -2
- package/template/src/lib/dashboard-themes.ts +140 -0
- package/template/src/lib/default-widgets.ts +14 -0
- package/template/src/lib/google-drive.ts +38 -30
- package/template/src/lib/permissions.ts +56 -1
- package/template/src/lib/prisma.ts +0 -1
- package/template/src/lib/widget-registry.ts +177 -0
- package/template/src/lib/workflow-executor.ts +7 -13
- package/README.md +0 -89
|
@@ -14,11 +14,7 @@ export default function EditWorkflowPage() {
|
|
|
14
14
|
title="Modifier une automatisation"
|
|
15
15
|
description="Ajustez le déclencheur et les actions de votre workflow."
|
|
16
16
|
/>
|
|
17
|
-
<div className="p-4 sm:p-6 lg:p-8">
|
|
18
|
-
{id ? <WorkflowEditor workflowId={id} /> : null}
|
|
19
|
-
</div>
|
|
17
|
+
<div className="p-4 sm:p-6 lg:p-8">{id ? <WorkflowEditor workflowId={id} /> : null}</div>
|
|
20
18
|
</div>
|
|
21
19
|
);
|
|
22
20
|
}
|
|
23
|
-
|
|
24
|
-
|
|
@@ -2,15 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
|
-
import {
|
|
6
|
-
Plus,
|
|
7
|
-
Trash2,
|
|
8
|
-
Mail,
|
|
9
|
-
MessageSquare,
|
|
10
|
-
Tag,
|
|
11
|
-
CheckSquare,
|
|
12
|
-
Clock,
|
|
13
|
-
} from 'lucide-react';
|
|
5
|
+
import { Plus, Trash2, Mail, MessageSquare, Tag, CheckSquare, Clock } from 'lucide-react';
|
|
14
6
|
import { cn } from '@/lib/utils';
|
|
15
7
|
|
|
16
8
|
interface Status {
|
|
@@ -131,10 +123,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
131
123
|
// Fermer le menu d'actions en cliquant en dehors
|
|
132
124
|
useEffect(() => {
|
|
133
125
|
const handleClickOutside = (event: MouseEvent) => {
|
|
134
|
-
if (
|
|
135
|
-
actionMenuRef.current &&
|
|
136
|
-
!actionMenuRef.current.contains(event.target as Node)
|
|
137
|
-
) {
|
|
126
|
+
if (actionMenuRef.current && !actionMenuRef.current.contains(event.target as Node)) {
|
|
138
127
|
setShowActionMenu(false);
|
|
139
128
|
}
|
|
140
129
|
};
|
|
@@ -308,15 +297,15 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
308
297
|
|
|
309
298
|
<div className="relative space-y-4">
|
|
310
299
|
{/* Ligne verticale */}
|
|
311
|
-
<div className="pointer-events-none absolute left-6
|
|
300
|
+
<div className="pointer-events-none absolute top-0 left-6 h-full w-px bg-linear-to-b from-indigo-300 via-gray-200 to-gray-200" />
|
|
312
301
|
|
|
313
302
|
{/* Déclencheur */}
|
|
314
303
|
<div className="relative pl-10">
|
|
315
|
-
<div className="absolute left-5
|
|
304
|
+
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-white bg-indigo-500 shadow-sm" />
|
|
316
305
|
<div className="rounded-xl border border-indigo-100 bg-white px-4 py-3 shadow-sm">
|
|
317
306
|
<div className="flex items-center justify-between">
|
|
318
307
|
<div>
|
|
319
|
-
<p className="text-xs font-semibold
|
|
308
|
+
<p className="text-xs font-semibold tracking-wide text-indigo-600 uppercase">
|
|
320
309
|
Déclencheur
|
|
321
310
|
</p>
|
|
322
311
|
<p className="mt-1 text-sm font-medium text-gray-900">
|
|
@@ -330,7 +319,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
330
319
|
{/* Actions */}
|
|
331
320
|
{actions.length === 0 ? (
|
|
332
321
|
<div className="relative pl-10">
|
|
333
|
-
<div className="absolute left-5
|
|
322
|
+
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-dashed border-gray-300 bg-white" />
|
|
334
323
|
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-gray-300 bg-gray-50 px-4 py-8 text-center">
|
|
335
324
|
<Clock className="mb-2 h-8 w-8 text-gray-300" />
|
|
336
325
|
<p className="text-sm font-medium text-gray-900">Aucune action définie</p>
|
|
@@ -342,7 +331,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
342
331
|
) : (
|
|
343
332
|
actions.map((action, index) => (
|
|
344
333
|
<div key={index} className="relative pl-10">
|
|
345
|
-
<div className="absolute left-5
|
|
334
|
+
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-white bg-indigo-500 shadow-sm" />
|
|
346
335
|
<div className="rounded-xl border border-gray-200 bg-white p-4 shadow-sm transition-shadow hover:shadow-md">
|
|
347
336
|
<div className="mb-3 flex items-center justify-between">
|
|
348
337
|
<div className="flex items-center gap-3">
|
|
@@ -410,9 +399,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
410
399
|
/>
|
|
411
400
|
</div>
|
|
412
401
|
<div>
|
|
413
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
414
|
-
Heures
|
|
415
|
-
</label>
|
|
402
|
+
<label className="block text-xs font-medium text-gray-700">Heures</label>
|
|
416
403
|
<input
|
|
417
404
|
type="number"
|
|
418
405
|
min="0"
|
|
@@ -580,7 +567,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
580
567
|
|
|
581
568
|
{/* Bouton ajouter une action */}
|
|
582
569
|
<div className="relative pl-10">
|
|
583
|
-
<div className="absolute left-5
|
|
570
|
+
<div className="absolute top-6 left-5 h-3 w-3 -translate-x-1/2 rounded-full border border-dashed border-indigo-300 bg-white" />
|
|
584
571
|
<div className="flex items-center justify-center">
|
|
585
572
|
<div className="relative inline-flex items-center" ref={actionMenuRef}>
|
|
586
573
|
<button
|
|
@@ -592,8 +579,8 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
592
579
|
Ajouter une action
|
|
593
580
|
</button>
|
|
594
581
|
{showActionMenu && (
|
|
595
|
-
<div className="absolute left-1/2
|
|
596
|
-
<p className="px-2 pb-1 text-[11px] font-medium
|
|
582
|
+
<div className="absolute top-full left-1/2 z-10 mt-2 w-60 -translate-x-1/2 rounded-xl border border-gray-200 bg-white p-2 text-xs shadow-lg">
|
|
583
|
+
<p className="px-2 pb-1 text-[11px] font-medium tracking-wide text-gray-500 uppercase">
|
|
597
584
|
Types d’actions
|
|
598
585
|
</p>
|
|
599
586
|
<button
|
|
@@ -664,7 +651,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
664
651
|
<div className="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
|
665
652
|
<div className="mb-4 flex items-center justify-between">
|
|
666
653
|
<div>
|
|
667
|
-
<p className="text-xs font-semibold
|
|
654
|
+
<p className="text-xs font-semibold tracking-wide text-gray-500 uppercase">
|
|
668
655
|
Paramètres du workflow
|
|
669
656
|
</p>
|
|
670
657
|
<p className="mt-1 text-sm font-medium text-gray-900">
|
|
@@ -674,9 +661,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
674
661
|
<span
|
|
675
662
|
className={cn(
|
|
676
663
|
'inline-flex items-center rounded-full px-2.5 py-0.5 text-[11px] font-medium',
|
|
677
|
-
formData.active
|
|
678
|
-
? 'bg-green-50 text-green-700'
|
|
679
|
-
: 'bg-gray-100 text-gray-600',
|
|
664
|
+
formData.active ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600',
|
|
680
665
|
)}
|
|
681
666
|
>
|
|
682
667
|
{formData.active ? 'Actif' : 'Brouillon'}
|
|
@@ -685,9 +670,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
685
670
|
|
|
686
671
|
<div className="space-y-4">
|
|
687
672
|
<div>
|
|
688
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
689
|
-
Nom du workflow *
|
|
690
|
-
</label>
|
|
673
|
+
<label className="block text-xs font-medium text-gray-700">Nom du workflow *</label>
|
|
691
674
|
<input
|
|
692
675
|
type="text"
|
|
693
676
|
required
|
|
@@ -704,9 +687,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
704
687
|
</div>
|
|
705
688
|
|
|
706
689
|
<div>
|
|
707
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
708
|
-
Description
|
|
709
|
-
</label>
|
|
690
|
+
<label className="block text-xs font-medium text-gray-700">Description</label>
|
|
710
691
|
<textarea
|
|
711
692
|
value={formData.description ?? ''}
|
|
712
693
|
onChange={(e) =>
|
|
@@ -754,7 +735,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
754
735
|
</div>
|
|
755
736
|
</div>
|
|
756
737
|
|
|
757
|
-
<div className="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm
|
|
738
|
+
<div className="space-y-4 rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
|
758
739
|
<div>
|
|
759
740
|
<label className="block text-xs font-medium text-gray-700">
|
|
760
741
|
Type de déclencheur *
|
|
@@ -801,9 +782,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
801
782
|
</select>
|
|
802
783
|
</div>
|
|
803
784
|
<div>
|
|
804
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
805
|
-
Vers le statut
|
|
806
|
-
</label>
|
|
785
|
+
<label className="block text-xs font-medium text-gray-700">Vers le statut</label>
|
|
807
786
|
<select
|
|
808
787
|
value={formData.triggerToStatusId ?? ''}
|
|
809
788
|
onChange={(e) =>
|
|
@@ -828,9 +807,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
828
807
|
{formData.triggerType === 'TIME_BASED' && (
|
|
829
808
|
<div className="grid gap-3 md:grid-cols-2">
|
|
830
809
|
<div>
|
|
831
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
832
|
-
Délai (jours)
|
|
833
|
-
</label>
|
|
810
|
+
<label className="block text-xs font-medium text-gray-700">Délai (jours)</label>
|
|
834
811
|
<input
|
|
835
812
|
type="number"
|
|
836
813
|
min="0"
|
|
@@ -845,9 +822,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
845
822
|
/>
|
|
846
823
|
</div>
|
|
847
824
|
<div>
|
|
848
|
-
<label className="block text-xs font-medium text-gray-700">
|
|
849
|
-
Délai (heures)
|
|
850
|
-
</label>
|
|
825
|
+
<label className="block text-xs font-medium text-gray-700">Délai (heures)</label>
|
|
851
826
|
<input
|
|
852
827
|
type="number"
|
|
853
828
|
min="0"
|
|
@@ -891,7 +866,7 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
891
866
|
<button
|
|
892
867
|
type="submit"
|
|
893
868
|
disabled={saving || loading}
|
|
894
|
-
className="w-full cursor-pointer rounded-xl bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-indigo-700 focus:
|
|
869
|
+
className="w-full cursor-pointer rounded-xl bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:bg-indigo-400 sm:w-auto"
|
|
895
870
|
>
|
|
896
871
|
{saving ? 'Enregistrement...' : 'Enregistrer le workflow'}
|
|
897
872
|
</button>
|
|
@@ -901,5 +876,3 @@ export function WorkflowEditor({ workflowId }: WorkflowEditorProps) {
|
|
|
901
876
|
</form>
|
|
902
877
|
);
|
|
903
878
|
}
|
|
904
|
-
|
|
905
|
-
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
Eye,
|
|
16
16
|
} from 'lucide-react';
|
|
17
17
|
import { cn } from '@/lib/utils';
|
|
18
|
-
import { useMobileMenuContext } from '@/contexts/mobile-menu-context';
|
|
19
18
|
|
|
20
19
|
interface Status {
|
|
21
20
|
id: string;
|
|
@@ -225,7 +224,6 @@ function createDefaultColumns(statuses: Status[]): ClosingColumn[] {
|
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
export default function ClosingPage() {
|
|
228
|
-
const { toggle: toggleMobileMenu, isOpen: isMobileMenuOpen } = useMobileMenuContext();
|
|
229
227
|
const [statuses, setStatuses] = useState<Status[]>([]);
|
|
230
228
|
const [contacts, setContacts] = useState<Contact[]>([]);
|
|
231
229
|
const [loading, setLoading] = useState(true);
|
|
@@ -571,37 +569,11 @@ export default function ClosingPage() {
|
|
|
571
569
|
return (
|
|
572
570
|
<div className="h-full">
|
|
573
571
|
<div className="border-b border-gray-200 bg-white px-4 py-4 sm:px-6 lg:px-8 lg:py-6">
|
|
574
|
-
<div
|
|
575
|
-
|
|
576
|
-
<
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
aria-label="Toggle menu"
|
|
580
|
-
>
|
|
581
|
-
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
582
|
-
{isMobileMenuOpen ? (
|
|
583
|
-
<path
|
|
584
|
-
strokeLinecap="round"
|
|
585
|
-
strokeLinejoin="round"
|
|
586
|
-
strokeWidth={2}
|
|
587
|
-
d="M6 18L18 6M6 6l12 12"
|
|
588
|
-
/>
|
|
589
|
-
) : (
|
|
590
|
-
<path
|
|
591
|
-
strokeLinecap="round"
|
|
592
|
-
strokeLinejoin="round"
|
|
593
|
-
strokeWidth={2}
|
|
594
|
-
d="M4 6h16M4 12h16M4 18h16"
|
|
595
|
-
/>
|
|
596
|
-
)}
|
|
597
|
-
</svg>
|
|
598
|
-
</button>
|
|
599
|
-
<div className="min-w-0 flex-1">
|
|
600
|
-
<h1 className="text-xl font-bold text-gray-900 sm:text-2xl">Pipeline de Closing</h1>
|
|
601
|
-
<p className="mt-1 text-sm text-gray-600">
|
|
602
|
-
Visualisez et gérez vos opportunités commerciales
|
|
603
|
-
</p>
|
|
604
|
-
</div>
|
|
572
|
+
<div>
|
|
573
|
+
<h1 className="text-xl font-bold text-gray-900 sm:text-2xl">Pipeline de Closing</h1>
|
|
574
|
+
<p className="mt-1 text-sm text-gray-600">
|
|
575
|
+
Visualisez et gérez vos opportunités commerciales
|
|
576
|
+
</p>
|
|
605
577
|
</div>
|
|
606
578
|
</div>
|
|
607
579
|
<div className="p-4 sm:p-6 lg:p-8">
|
|
@@ -620,30 +592,6 @@ export default function ClosingPage() {
|
|
|
620
592
|
{/* En-tête personnalisé avec filtres intégrés */}
|
|
621
593
|
<div className="border-b border-gray-200 bg-white px-4 py-4 sm:px-6 lg:px-8 lg:py-6">
|
|
622
594
|
<div className="flex items-start gap-3">
|
|
623
|
-
{/* Mobile menu button */}
|
|
624
|
-
<button
|
|
625
|
-
onClick={toggleMobileMenu}
|
|
626
|
-
className="mt-1 shrink-0 cursor-pointer rounded-lg p-2 text-gray-700 transition-colors hover:bg-gray-100 lg:hidden"
|
|
627
|
-
aria-label="Toggle menu"
|
|
628
|
-
>
|
|
629
|
-
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
630
|
-
{isMobileMenuOpen ? (
|
|
631
|
-
<path
|
|
632
|
-
strokeLinecap="round"
|
|
633
|
-
strokeLinejoin="round"
|
|
634
|
-
strokeWidth={2}
|
|
635
|
-
d="M6 18L18 6M6 6l12 12"
|
|
636
|
-
/>
|
|
637
|
-
) : (
|
|
638
|
-
<path
|
|
639
|
-
strokeLinecap="round"
|
|
640
|
-
strokeLinejoin="round"
|
|
641
|
-
strokeWidth={2}
|
|
642
|
-
d="M4 6h16M4 12h16M4 18h16"
|
|
643
|
-
/>
|
|
644
|
-
)}
|
|
645
|
-
</svg>
|
|
646
|
-
</button>
|
|
647
595
|
<div className="flex min-w-0 flex-1 items-start justify-between gap-4">
|
|
648
596
|
<div className="min-w-0 flex-1">
|
|
649
597
|
<h1 className="text-xl font-bold text-gray-900 sm:text-2xl">Pipeline de Closing</h1>
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
Upload,
|
|
23
23
|
File,
|
|
24
24
|
Target,
|
|
25
|
+
ChevronDown,
|
|
25
26
|
} from 'lucide-react';
|
|
26
27
|
import Link from 'next/link';
|
|
27
28
|
import { Editor, type DefaultTemplateRef } from '@/components/editor';
|
|
@@ -42,6 +43,25 @@ interface User {
|
|
|
42
43
|
role?: string;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function getNewsFeedCardColor(type: string): string {
|
|
47
|
+
switch (type) {
|
|
48
|
+
case 'STATUS_CHANGE':
|
|
49
|
+
return 'bg-purple-50 border-purple-200';
|
|
50
|
+
case 'CONTACT_UPDATE':
|
|
51
|
+
return 'bg-indigo-50 border-indigo-200';
|
|
52
|
+
case 'ASSIGNMENT_CHANGE':
|
|
53
|
+
return 'bg-teal-50 border-teal-200';
|
|
54
|
+
case 'FILE_UPLOADED':
|
|
55
|
+
return 'bg-green-50 border-green-200';
|
|
56
|
+
case 'FILE_REPLACED':
|
|
57
|
+
return 'bg-orange-50 border-orange-200';
|
|
58
|
+
case 'FILE_DELETED':
|
|
59
|
+
return 'bg-red-50 border-red-200';
|
|
60
|
+
default:
|
|
61
|
+
return 'bg-gray-50 border-gray-200';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
interface Interaction {
|
|
46
66
|
id: string;
|
|
47
67
|
type:
|
|
@@ -132,6 +152,7 @@ export default function ContactDetailPage() {
|
|
|
132
152
|
const [tasks, setTasks] = useState<any[]>([]);
|
|
133
153
|
const [sendingEmail, setSendingEmail] = useState(false);
|
|
134
154
|
const [creatingTask, setCreatingTask] = useState(false);
|
|
155
|
+
const [showNewsFeed, setShowNewsFeed] = useState(false);
|
|
135
156
|
const [emailTemplates, setEmailTemplates] = useState<any[]>([]);
|
|
136
157
|
const [noteTemplates, setNoteTemplates] = useState<any[]>([]);
|
|
137
158
|
const emailEditorRef = useRef<DefaultTemplateRef | null>(null);
|
|
@@ -207,6 +228,7 @@ export default function ContactDetailPage() {
|
|
|
207
228
|
});
|
|
208
229
|
const meetEditorRef = useRef<DefaultTemplateRef | null>(null);
|
|
209
230
|
const [googleAccountConnected, setGoogleAccountConnected] = useState(false);
|
|
231
|
+
const [googleDriveConnected, setGoogleDriveConnected] = useState(false);
|
|
210
232
|
const [smtpConfigured, setSmtpConfigured] = useState<boolean | null>(null);
|
|
211
233
|
|
|
212
234
|
// Modal d'édition Google Meet
|
|
@@ -509,13 +531,15 @@ export default function ContactDetailPage() {
|
|
|
509
531
|
useEffect(() => {
|
|
510
532
|
const checkIntegrations = async () => {
|
|
511
533
|
try {
|
|
512
|
-
// Statut Google
|
|
534
|
+
// Statut Google (Drive admin + Calendar personnel)
|
|
513
535
|
const googleResponse = await fetch('/api/auth/google/status');
|
|
514
536
|
if (googleResponse.ok) {
|
|
515
537
|
const data = await googleResponse.json();
|
|
516
|
-
setGoogleAccountConnected(!!data.connected);
|
|
538
|
+
setGoogleAccountConnected(!!data.calendar?.connected);
|
|
539
|
+
setGoogleDriveConnected(!!data.drive?.connected);
|
|
517
540
|
} else {
|
|
518
541
|
setGoogleAccountConnected(false);
|
|
542
|
+
setGoogleDriveConnected(false);
|
|
519
543
|
}
|
|
520
544
|
|
|
521
545
|
// Configuration SMTP
|
|
@@ -652,9 +676,9 @@ export default function ContactDetailPage() {
|
|
|
652
676
|
setError('');
|
|
653
677
|
|
|
654
678
|
try {
|
|
655
|
-
if (!
|
|
679
|
+
if (!googleDriveConnected) {
|
|
656
680
|
setError(
|
|
657
|
-
'
|
|
681
|
+
'Aucun compte Google Drive configuré. Veuillez demander à un administrateur de connecter son compte Google Drive dans les paramètres.',
|
|
658
682
|
);
|
|
659
683
|
setUploadingFile(false);
|
|
660
684
|
setUploadProgress(0);
|
|
@@ -2591,44 +2615,36 @@ export default function ContactDetailPage() {
|
|
|
2591
2615
|
|
|
2592
2616
|
{/* Section Fil d'actualités */}
|
|
2593
2617
|
<div>
|
|
2594
|
-
<
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
return 'bg-orange-50 border-orange-200';
|
|
2620
|
-
case 'FILE_DELETED':
|
|
2621
|
-
return 'bg-red-50 border-red-200';
|
|
2622
|
-
default:
|
|
2623
|
-
return 'bg-gray-50 border-gray-200';
|
|
2624
|
-
}
|
|
2625
|
-
};
|
|
2626
|
-
return (
|
|
2618
|
+
<button
|
|
2619
|
+
type="button"
|
|
2620
|
+
onClick={() => setShowNewsFeed((prev) => !prev)}
|
|
2621
|
+
className="mb-4 flex w-full cursor-pointer items-center justify-between rounded-xl border border-transparent px-3 py-2 text-left transition-colors hover:border-gray-200 hover:bg-gray-50"
|
|
2622
|
+
>
|
|
2623
|
+
<span className="text-lg font-semibold text-gray-900">Fil d'actualités</span>
|
|
2624
|
+
<ChevronDown
|
|
2625
|
+
className={cn(
|
|
2626
|
+
'h-4 w-4 text-gray-500 transition-transform',
|
|
2627
|
+
showNewsFeed ? 'rotate-180' : 'rotate-0',
|
|
2628
|
+
)}
|
|
2629
|
+
/>
|
|
2630
|
+
</button>
|
|
2631
|
+
{showNewsFeed && (
|
|
2632
|
+
<div className="space-y-4">
|
|
2633
|
+
{Object.keys(groupedNewsFeed).length === 0 ? (
|
|
2634
|
+
<p className="py-6 text-center text-sm text-gray-500">
|
|
2635
|
+
Aucune modification
|
|
2636
|
+
</p>
|
|
2637
|
+
) : (
|
|
2638
|
+
Object.entries(groupedNewsFeed).map(([date, interactions]) => (
|
|
2639
|
+
<div key={date}>
|
|
2640
|
+
<h3 className="mb-3 text-sm font-semibold text-gray-700">{date}</h3>
|
|
2641
|
+
<div className="space-y-2">
|
|
2642
|
+
{interactions.map((interaction) => (
|
|
2627
2643
|
<div
|
|
2628
2644
|
key={interaction.id}
|
|
2629
2645
|
className={cn(
|
|
2630
2646
|
'relative rounded-lg border p-3 sm:p-4',
|
|
2631
|
-
|
|
2647
|
+
getNewsFeedCardColor(interaction.type),
|
|
2632
2648
|
)}
|
|
2633
2649
|
>
|
|
2634
2650
|
<div className="flex items-start gap-2 sm:gap-3">
|
|
@@ -2664,13 +2680,13 @@ export default function ContactDetailPage() {
|
|
|
2664
2680
|
</div>
|
|
2665
2681
|
</div>
|
|
2666
2682
|
</div>
|
|
2667
|
-
)
|
|
2668
|
-
|
|
2683
|
+
))}
|
|
2684
|
+
</div>
|
|
2669
2685
|
</div>
|
|
2670
|
-
|
|
2671
|
-
)
|
|
2672
|
-
|
|
2673
|
-
|
|
2686
|
+
))
|
|
2687
|
+
)}
|
|
2688
|
+
</div>
|
|
2689
|
+
)}
|
|
2674
2690
|
</div>
|
|
2675
2691
|
</div>
|
|
2676
2692
|
)}
|