@hed-hog/contact 0.0.279 → 0.0.285
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/README.md +2 -0
- package/dist/person/dto/create-followup.dto.d.ts +5 -0
- package/dist/person/dto/create-followup.dto.d.ts.map +1 -0
- package/dist/person/dto/create-followup.dto.js +31 -0
- package/dist/person/dto/create-followup.dto.js.map +1 -0
- package/dist/person/dto/create-interaction.dto.d.ts +12 -0
- package/dist/person/dto/create-interaction.dto.d.ts.map +1 -0
- package/dist/person/dto/create-interaction.dto.js +39 -0
- package/dist/person/dto/create-interaction.dto.js.map +1 -0
- package/dist/person/dto/create.dto.d.ts +24 -0
- package/dist/person/dto/create.dto.d.ts.map +1 -1
- package/dist/person/dto/create.dto.js +56 -1
- package/dist/person/dto/create.dto.js.map +1 -1
- package/dist/person/dto/duplicates-query.dto.d.ts +8 -0
- package/dist/person/dto/duplicates-query.dto.d.ts.map +1 -0
- package/dist/person/dto/duplicates-query.dto.js +45 -0
- package/dist/person/dto/duplicates-query.dto.js.map +1 -0
- package/dist/person/dto/merge.dto.d.ts +6 -0
- package/dist/person/dto/merge.dto.d.ts.map +1 -0
- package/dist/person/dto/merge.dto.js +35 -0
- package/dist/person/dto/merge.dto.js.map +1 -0
- package/dist/person/dto/update-lifecycle-stage.dto.d.ts +13 -0
- package/dist/person/dto/update-lifecycle-stage.dto.d.ts.map +1 -0
- package/dist/person/dto/update-lifecycle-stage.dto.js +34 -0
- package/dist/person/dto/update-lifecycle-stage.dto.js.map +1 -0
- package/dist/person/dto/update.dto.d.ts +8 -1
- package/dist/person/dto/update.dto.d.ts.map +1 -1
- package/dist/person/dto/update.dto.js +36 -0
- package/dist/person/dto/update.dto.js.map +1 -1
- package/dist/person/person.controller.d.ts +57 -1
- package/dist/person/person.controller.d.ts.map +1 -1
- package/dist/person/person.controller.js +85 -3
- package/dist/person/person.controller.js.map +1 -1
- package/dist/person/person.service.d.ts +79 -0
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +730 -9
- package/dist/person/person.service.js.map +1 -1
- package/hedhog/data/route.yaml +18 -0
- package/hedhog/frontend/app/_components/crm-coming-soon.tsx.ejs +110 -110
- package/hedhog/frontend/app/_components/crm-nav.tsx.ejs +73 -73
- package/hedhog/frontend/app/_lib/crm-mocks.ts.ejs +498 -256
- package/hedhog/frontend/app/_lib/crm-sections.tsx.ejs +81 -81
- package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +477 -0
- package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +62 -0
- package/hedhog/frontend/app/accounts/page.tsx.ejs +886 -15
- package/hedhog/frontend/app/activities/page.tsx.ejs +15 -15
- package/hedhog/frontend/app/contact-type/page.tsx.ejs +105 -91
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +506 -573
- package/hedhog/frontend/app/document-type/page.tsx.ejs +105 -91
- package/hedhog/frontend/app/follow-ups/page.tsx.ejs +15 -15
- package/hedhog/frontend/app/page.tsx.ejs +5 -5
- package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +1440 -1103
- package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +4 -3
- package/hedhog/frontend/app/person/_components/person-types.ts.ejs +14 -0
- package/hedhog/frontend/app/person/page.tsx.ejs +108 -190
- package/hedhog/frontend/app/pipeline/_components/lead-detail-sheet.tsx.ejs +599 -0
- package/hedhog/frontend/app/pipeline/page.tsx.ejs +1074 -299
- package/hedhog/frontend/app/reports/page.tsx.ejs +15 -15
- package/hedhog/frontend/messages/en.json +107 -0
- package/hedhog/frontend/messages/pt.json +106 -0
- package/package.json +6 -6
- package/src/person/dto/create-followup.dto.ts +15 -0
- package/src/person/dto/create-interaction.dto.ts +23 -0
- package/src/person/dto/create.dto.ts +50 -0
- package/src/person/dto/duplicates-query.dto.ts +34 -0
- package/src/person/dto/merge.dto.ts +15 -0
- package/src/person/dto/update-lifecycle-stage.dto.ts +19 -0
- package/src/person/dto/update.dto.ts +31 -1
- package/src/person/person.controller.ts +63 -2
- package/src/person/person.service.ts +1096 -7
|
@@ -119,9 +119,10 @@ export function PersonInteractionDialog({
|
|
|
119
119
|
try {
|
|
120
120
|
setIsSubmitting(true);
|
|
121
121
|
await request({
|
|
122
|
-
url: `/person/${person.id}`,
|
|
123
|
-
method: '
|
|
124
|
-
data:
|
|
122
|
+
url: `/person/${person.id}/followup`,
|
|
123
|
+
method: 'POST',
|
|
124
|
+
data: values,
|
|
125
|
+
showErrors: false,
|
|
125
126
|
});
|
|
126
127
|
toast.success(t('followupSuccess'));
|
|
127
128
|
onOpenChange(false);
|
|
@@ -128,6 +128,11 @@ export type Person = {
|
|
|
128
128
|
source?: PersonSource | null;
|
|
129
129
|
lifecycle_stage?: PersonLifecycleStage | null;
|
|
130
130
|
next_action_at?: string | null;
|
|
131
|
+
score?: number | null;
|
|
132
|
+
deal_value?: number | null;
|
|
133
|
+
tags?: string[] | null;
|
|
134
|
+
last_interaction_at?: string | null;
|
|
135
|
+
interaction_count?: number | null;
|
|
131
136
|
employer_company_id?: number | null;
|
|
132
137
|
employer_company?: PersonRelationSummary | null;
|
|
133
138
|
created_at: string;
|
|
@@ -136,6 +141,15 @@ export type Person = {
|
|
|
136
141
|
document?: PersonDocument[];
|
|
137
142
|
};
|
|
138
143
|
|
|
144
|
+
export type PersonInteraction = {
|
|
145
|
+
id: number;
|
|
146
|
+
type: PersonInteractionType;
|
|
147
|
+
notes?: string | null;
|
|
148
|
+
created_at: string;
|
|
149
|
+
user_id?: number | null;
|
|
150
|
+
user_name?: string | null;
|
|
151
|
+
};
|
|
152
|
+
|
|
139
153
|
export type ContactTypeOption = {
|
|
140
154
|
id: number;
|
|
141
155
|
code: string;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { CrmNav } from '../_components/crm-nav';
|
|
4
3
|
import { CopyButton } from '@/components/copy-button';
|
|
5
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
EmptyState,
|
|
6
|
+
Page,
|
|
7
|
+
PageHeader,
|
|
8
|
+
PaginationFooter,
|
|
9
|
+
SearchBar,
|
|
10
|
+
type SearchBarControl,
|
|
11
|
+
StatsCards,
|
|
12
|
+
} from '@/components/entity-list';
|
|
6
13
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
7
14
|
import { Badge } from '@/components/ui/badge';
|
|
8
15
|
import { Button } from '@/components/ui/button';
|
|
@@ -14,14 +21,6 @@ import {
|
|
|
14
21
|
DropdownMenuSeparator,
|
|
15
22
|
DropdownMenuTrigger,
|
|
16
23
|
} from '@/components/ui/dropdown-menu';
|
|
17
|
-
import { Input } from '@/components/ui/input';
|
|
18
|
-
import {
|
|
19
|
-
Select,
|
|
20
|
-
SelectContent,
|
|
21
|
-
SelectItem,
|
|
22
|
-
SelectTrigger,
|
|
23
|
-
SelectValue,
|
|
24
|
-
} from '@/components/ui/select';
|
|
25
24
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
26
25
|
import {
|
|
27
26
|
Table,
|
|
@@ -48,10 +47,6 @@ import {
|
|
|
48
47
|
Building2,
|
|
49
48
|
CalendarDays,
|
|
50
49
|
CheckCircle2,
|
|
51
|
-
ChevronLeft,
|
|
52
|
-
ChevronRight,
|
|
53
|
-
ChevronsLeft,
|
|
54
|
-
ChevronsRight,
|
|
55
50
|
FileText,
|
|
56
51
|
LayoutGrid,
|
|
57
52
|
List,
|
|
@@ -61,7 +56,6 @@ import {
|
|
|
61
56
|
Pencil,
|
|
62
57
|
Phone,
|
|
63
58
|
Plus,
|
|
64
|
-
Search,
|
|
65
59
|
Trash2,
|
|
66
60
|
User,
|
|
67
61
|
UserCheck,
|
|
@@ -718,6 +712,74 @@ export default function PeoplePage() {
|
|
|
718
712
|
|
|
719
713
|
const peopleRows = table.getRowModel().rows;
|
|
720
714
|
|
|
715
|
+
const statsCards = [
|
|
716
|
+
{
|
|
717
|
+
title: t('statsTotal'),
|
|
718
|
+
value: stats.total,
|
|
719
|
+
icon: <Users className="h-5 w-5" />,
|
|
720
|
+
iconBgColor: 'bg-slate-100',
|
|
721
|
+
iconColor: 'text-slate-700',
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
title: t('statsActive'),
|
|
725
|
+
value: stats.active,
|
|
726
|
+
icon: <CheckCircle2 className="h-5 w-5" />,
|
|
727
|
+
iconBgColor: 'bg-green-50',
|
|
728
|
+
iconColor: 'text-green-600',
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
title: t('statsIndividual'),
|
|
732
|
+
value: stats.individual,
|
|
733
|
+
icon: <UserCheck className="h-5 w-5" />,
|
|
734
|
+
iconBgColor: 'bg-blue-50',
|
|
735
|
+
iconColor: 'text-blue-600',
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
title: t('statsCompany'),
|
|
739
|
+
value: stats.company,
|
|
740
|
+
icon: <Building2 className="h-5 w-5" />,
|
|
741
|
+
iconBgColor: 'bg-amber-50',
|
|
742
|
+
iconColor: 'text-amber-600',
|
|
743
|
+
},
|
|
744
|
+
];
|
|
745
|
+
|
|
746
|
+
const searchControls: SearchBarControl[] = [
|
|
747
|
+
...(allowCompanyRegistration
|
|
748
|
+
? [
|
|
749
|
+
{
|
|
750
|
+
id: 'type-filter',
|
|
751
|
+
type: 'select' as const,
|
|
752
|
+
value: typeFilter,
|
|
753
|
+
onChange: (value: string) => {
|
|
754
|
+
setTypeFilter(value);
|
|
755
|
+
setPage(1);
|
|
756
|
+
},
|
|
757
|
+
placeholder: t('filterByType'),
|
|
758
|
+
options: [
|
|
759
|
+
{ value: 'all', label: t('allTypes') },
|
|
760
|
+
{ value: 'individual', label: t('individual') },
|
|
761
|
+
{ value: 'company', label: t('company') },
|
|
762
|
+
],
|
|
763
|
+
},
|
|
764
|
+
]
|
|
765
|
+
: []),
|
|
766
|
+
{
|
|
767
|
+
id: 'status-filter',
|
|
768
|
+
type: 'select',
|
|
769
|
+
value: statusFilter,
|
|
770
|
+
onChange: (value: string) => {
|
|
771
|
+
setStatusFilter(value);
|
|
772
|
+
setPage(1);
|
|
773
|
+
},
|
|
774
|
+
placeholder: t('filterByStatus'),
|
|
775
|
+
options: [
|
|
776
|
+
{ value: 'all', label: t('allStatuses') },
|
|
777
|
+
{ value: 'active', label: t('active') },
|
|
778
|
+
{ value: 'inactive', label: t('inactive') },
|
|
779
|
+
],
|
|
780
|
+
},
|
|
781
|
+
];
|
|
782
|
+
|
|
721
783
|
return (
|
|
722
784
|
<Page>
|
|
723
785
|
<PageHeader
|
|
@@ -745,104 +807,26 @@ export default function PeoplePage() {
|
|
|
745
807
|
]}
|
|
746
808
|
/>
|
|
747
809
|
|
|
748
|
-
<
|
|
749
|
-
|
|
750
|
-
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
|
751
|
-
<Card className="py-0">
|
|
752
|
-
<CardContent className="flex items-center justify-between p-4">
|
|
753
|
-
<div>
|
|
754
|
-
<p className="text-xs text-muted-foreground">{t('statsTotal')}</p>
|
|
755
|
-
<p className="text-xl font-semibold">{stats.total}</p>
|
|
756
|
-
</div>
|
|
757
|
-
<Users className="h-5 w-5 text-muted-foreground" />
|
|
758
|
-
</CardContent>
|
|
759
|
-
</Card>
|
|
760
|
-
<Card className="py-0">
|
|
761
|
-
<CardContent className="flex items-center justify-between p-4">
|
|
762
|
-
<div>
|
|
763
|
-
<p className="text-xs text-muted-foreground">
|
|
764
|
-
{t('statsActive')}
|
|
765
|
-
</p>
|
|
766
|
-
<p className="text-xl font-semibold">{stats.active}</p>
|
|
767
|
-
</div>
|
|
768
|
-
<CheckCircle2 className="h-5 w-5 text-green-600" />
|
|
769
|
-
</CardContent>
|
|
770
|
-
</Card>
|
|
771
|
-
<Card className="py-0">
|
|
772
|
-
<CardContent className="flex items-center justify-between p-4">
|
|
773
|
-
<div>
|
|
774
|
-
<p className="text-xs text-muted-foreground">
|
|
775
|
-
{t('statsIndividual')}
|
|
776
|
-
</p>
|
|
777
|
-
<p className="text-xl font-semibold">{stats.individual}</p>
|
|
778
|
-
</div>
|
|
779
|
-
<UserCheck className="h-5 w-5 text-blue-600" />
|
|
780
|
-
</CardContent>
|
|
781
|
-
</Card>
|
|
782
|
-
<Card className="py-0">
|
|
783
|
-
<CardContent className="flex items-center justify-between p-4">
|
|
784
|
-
<div>
|
|
785
|
-
<p className="text-xs text-muted-foreground">
|
|
786
|
-
{t('statsCompany')}
|
|
787
|
-
</p>
|
|
788
|
-
<p className="text-xl font-semibold">{stats.company}</p>
|
|
789
|
-
</div>
|
|
790
|
-
<Building2 className="h-5 w-5 text-amber-600" />
|
|
791
|
-
</CardContent>
|
|
792
|
-
</Card>
|
|
793
|
-
</div>
|
|
810
|
+
<StatsCards stats={statsCards} />
|
|
794
811
|
|
|
795
812
|
<div className="flex flex-col gap-4 xl:flex-row xl:items-center">
|
|
796
|
-
<div className="
|
|
797
|
-
<
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
setSearchInput(event.target.value);
|
|
813
|
+
<div className="flex-1">
|
|
814
|
+
<SearchBar
|
|
815
|
+
searchQuery={searchInput}
|
|
816
|
+
onSearchChange={(value) => {
|
|
817
|
+
setSearchInput(value);
|
|
802
818
|
setPage(1);
|
|
803
819
|
}}
|
|
820
|
+
onSearch={() => {
|
|
821
|
+
setPage(1);
|
|
822
|
+
void refetch();
|
|
823
|
+
}}
|
|
804
824
|
placeholder={t('searchByNamePlaceholder')}
|
|
805
|
-
|
|
825
|
+
controls={searchControls}
|
|
806
826
|
/>
|
|
807
827
|
</div>
|
|
808
828
|
|
|
809
829
|
<div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap xl:justify-end">
|
|
810
|
-
{allowCompanyRegistration ? (
|
|
811
|
-
<Select
|
|
812
|
-
value={typeFilter}
|
|
813
|
-
onValueChange={(value) => {
|
|
814
|
-
setTypeFilter(value);
|
|
815
|
-
setPage(1);
|
|
816
|
-
}}
|
|
817
|
-
>
|
|
818
|
-
<SelectTrigger className="w-full sm:w-44">
|
|
819
|
-
<SelectValue placeholder={t('filterByType')} />
|
|
820
|
-
</SelectTrigger>
|
|
821
|
-
<SelectContent>
|
|
822
|
-
<SelectItem value="all">{t('allTypes')}</SelectItem>
|
|
823
|
-
<SelectItem value="individual">{t('individual')}</SelectItem>
|
|
824
|
-
<SelectItem value="company">{t('company')}</SelectItem>
|
|
825
|
-
</SelectContent>
|
|
826
|
-
</Select>
|
|
827
|
-
) : null}
|
|
828
|
-
|
|
829
|
-
<Select
|
|
830
|
-
value={statusFilter}
|
|
831
|
-
onValueChange={(value) => {
|
|
832
|
-
setStatusFilter(value);
|
|
833
|
-
setPage(1);
|
|
834
|
-
}}
|
|
835
|
-
>
|
|
836
|
-
<SelectTrigger className="w-full sm:w-36">
|
|
837
|
-
<SelectValue placeholder={t('filterByStatus')} />
|
|
838
|
-
</SelectTrigger>
|
|
839
|
-
<SelectContent>
|
|
840
|
-
<SelectItem value="all">{t('allStatuses')}</SelectItem>
|
|
841
|
-
<SelectItem value="active">{t('active')}</SelectItem>
|
|
842
|
-
<SelectItem value="inactive">{t('inactive')}</SelectItem>
|
|
843
|
-
</SelectContent>
|
|
844
|
-
</Select>
|
|
845
|
-
|
|
846
830
|
<div className="flex items-center justify-between gap-3 sm:justify-start">
|
|
847
831
|
<span className="text-xs font-medium text-muted-foreground">
|
|
848
832
|
{t('viewMode')}
|
|
@@ -908,17 +892,14 @@ export default function PeoplePage() {
|
|
|
908
892
|
</div>
|
|
909
893
|
)
|
|
910
894
|
) : paginate.data.length === 0 ? (
|
|
911
|
-
<
|
|
912
|
-
<Users className="
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
{t('newPerson')}
|
|
920
|
-
</Button>
|
|
921
|
-
</div>
|
|
895
|
+
<EmptyState
|
|
896
|
+
icon={<Users className="h-12 w-12" />}
|
|
897
|
+
title={t('emptyStateTitle')}
|
|
898
|
+
description={t('emptyStateDescription')}
|
|
899
|
+
actionLabel={t('newPerson')}
|
|
900
|
+
actionIcon={<Plus className="mr-2 h-4 w-4" />}
|
|
901
|
+
onAction={openCreateSheet}
|
|
902
|
+
/>
|
|
922
903
|
) : (
|
|
923
904
|
<>
|
|
924
905
|
{viewMode === 'table' ? (
|
|
@@ -1230,81 +1211,18 @@ export default function PeoplePage() {
|
|
|
1230
1211
|
</div>
|
|
1231
1212
|
)}
|
|
1232
1213
|
|
|
1233
|
-
<div className="
|
|
1234
|
-
<
|
|
1235
|
-
{
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
onValueChange={(value) => {
|
|
1246
|
-
setPageSize(Number(value));
|
|
1247
|
-
setPage(1);
|
|
1248
|
-
}}
|
|
1249
|
-
>
|
|
1250
|
-
<SelectTrigger className="w-20">
|
|
1251
|
-
<SelectValue />
|
|
1252
|
-
</SelectTrigger>
|
|
1253
|
-
<SelectContent>
|
|
1254
|
-
{[12, 20, 30, 40, 50].map((size) => (
|
|
1255
|
-
<SelectItem key={size} value={String(size)}>
|
|
1256
|
-
{size}
|
|
1257
|
-
</SelectItem>
|
|
1258
|
-
))}
|
|
1259
|
-
</SelectContent>
|
|
1260
|
-
</Select>
|
|
1261
|
-
</div>
|
|
1262
|
-
|
|
1263
|
-
<div className="flex items-center gap-2">
|
|
1264
|
-
<Button
|
|
1265
|
-
variant="outline"
|
|
1266
|
-
size="icon"
|
|
1267
|
-
onClick={() => setPage(1)}
|
|
1268
|
-
disabled={page <= 1}
|
|
1269
|
-
>
|
|
1270
|
-
<ChevronsLeft className="h-4 w-4" />
|
|
1271
|
-
</Button>
|
|
1272
|
-
<Button
|
|
1273
|
-
variant="outline"
|
|
1274
|
-
size="icon"
|
|
1275
|
-
onClick={() =>
|
|
1276
|
-
setPage((previous) => Math.max(1, previous - 1))
|
|
1277
|
-
}
|
|
1278
|
-
disabled={page <= 1}
|
|
1279
|
-
>
|
|
1280
|
-
<ChevronLeft className="h-4 w-4" />
|
|
1281
|
-
</Button>
|
|
1282
|
-
<span className="min-w-32 text-center text-sm">
|
|
1283
|
-
{t('paginationLabel', {
|
|
1284
|
-
current: page,
|
|
1285
|
-
total: pageCount,
|
|
1286
|
-
})}
|
|
1287
|
-
</span>
|
|
1288
|
-
<Button
|
|
1289
|
-
variant="outline"
|
|
1290
|
-
size="icon"
|
|
1291
|
-
onClick={() =>
|
|
1292
|
-
setPage((previous) => Math.min(pageCount, previous + 1))
|
|
1293
|
-
}
|
|
1294
|
-
disabled={page >= pageCount}
|
|
1295
|
-
>
|
|
1296
|
-
<ChevronRight className="h-4 w-4" />
|
|
1297
|
-
</Button>
|
|
1298
|
-
<Button
|
|
1299
|
-
variant="outline"
|
|
1300
|
-
size="icon"
|
|
1301
|
-
onClick={() => setPage(pageCount)}
|
|
1302
|
-
disabled={page >= pageCount}
|
|
1303
|
-
>
|
|
1304
|
-
<ChevronsRight className="h-4 w-4" />
|
|
1305
|
-
</Button>
|
|
1306
|
-
</div>
|
|
1307
|
-
</div>
|
|
1214
|
+
<div className="border-t p-4">
|
|
1215
|
+
<PaginationFooter
|
|
1216
|
+
currentPage={page}
|
|
1217
|
+
pageSize={pageSize}
|
|
1218
|
+
totalItems={paginate.total}
|
|
1219
|
+
onPageChange={setPage}
|
|
1220
|
+
onPageSizeChange={(nextPageSize) => {
|
|
1221
|
+
setPageSize(nextPageSize);
|
|
1222
|
+
setPage(1);
|
|
1223
|
+
}}
|
|
1224
|
+
pageSizeOptions={[12, 20, 30, 40, 50]}
|
|
1225
|
+
/>
|
|
1308
1226
|
</div>
|
|
1309
1227
|
</>
|
|
1310
1228
|
)}
|