@hed-hog/contact 0.0.279 → 0.0.286

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.
Files changed (73) hide show
  1. package/README.md +2 -0
  2. package/dist/contact.service.d.ts +2 -148
  3. package/dist/contact.service.d.ts.map +1 -1
  4. package/dist/person/dto/create-followup.dto.d.ts +5 -0
  5. package/dist/person/dto/create-followup.dto.d.ts.map +1 -0
  6. package/dist/person/dto/create-followup.dto.js +31 -0
  7. package/dist/person/dto/create-followup.dto.js.map +1 -0
  8. package/dist/person/dto/create-interaction.dto.d.ts +12 -0
  9. package/dist/person/dto/create-interaction.dto.d.ts.map +1 -0
  10. package/dist/person/dto/create-interaction.dto.js +39 -0
  11. package/dist/person/dto/create-interaction.dto.js.map +1 -0
  12. package/dist/person/dto/create.dto.d.ts +24 -0
  13. package/dist/person/dto/create.dto.d.ts.map +1 -1
  14. package/dist/person/dto/create.dto.js +56 -1
  15. package/dist/person/dto/create.dto.js.map +1 -1
  16. package/dist/person/dto/duplicates-query.dto.d.ts +8 -0
  17. package/dist/person/dto/duplicates-query.dto.d.ts.map +1 -0
  18. package/dist/person/dto/duplicates-query.dto.js +45 -0
  19. package/dist/person/dto/duplicates-query.dto.js.map +1 -0
  20. package/dist/person/dto/merge.dto.d.ts +6 -0
  21. package/dist/person/dto/merge.dto.d.ts.map +1 -0
  22. package/dist/person/dto/merge.dto.js +35 -0
  23. package/dist/person/dto/merge.dto.js.map +1 -0
  24. package/dist/person/dto/update-lifecycle-stage.dto.d.ts +13 -0
  25. package/dist/person/dto/update-lifecycle-stage.dto.d.ts.map +1 -0
  26. package/dist/person/dto/update-lifecycle-stage.dto.js +34 -0
  27. package/dist/person/dto/update-lifecycle-stage.dto.js.map +1 -0
  28. package/dist/person/dto/update.dto.d.ts +8 -1
  29. package/dist/person/dto/update.dto.d.ts.map +1 -1
  30. package/dist/person/dto/update.dto.js +36 -0
  31. package/dist/person/dto/update.dto.js.map +1 -1
  32. package/dist/person/person.controller.d.ts +57 -1
  33. package/dist/person/person.controller.d.ts.map +1 -1
  34. package/dist/person/person.controller.js +85 -3
  35. package/dist/person/person.controller.js.map +1 -1
  36. package/dist/person/person.service.d.ts +79 -0
  37. package/dist/person/person.service.d.ts.map +1 -1
  38. package/dist/person/person.service.js +730 -9
  39. package/dist/person/person.service.js.map +1 -1
  40. package/hedhog/data/route.yaml +18 -0
  41. package/hedhog/frontend/app/_components/crm-coming-soon.tsx.ejs +110 -110
  42. package/hedhog/frontend/app/_components/crm-nav.tsx.ejs +73 -73
  43. package/hedhog/frontend/app/_lib/crm-mocks.ts.ejs +498 -256
  44. package/hedhog/frontend/app/_lib/crm-sections.tsx.ejs +81 -81
  45. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +477 -0
  46. package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +62 -0
  47. package/hedhog/frontend/app/accounts/page.tsx.ejs +892 -15
  48. package/hedhog/frontend/app/activities/page.tsx.ejs +812 -15
  49. package/hedhog/frontend/app/contact-type/page.tsx.ejs +105 -91
  50. package/hedhog/frontend/app/dashboard/page.tsx.ejs +491 -573
  51. package/hedhog/frontend/app/document-type/page.tsx.ejs +105 -91
  52. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +696 -15
  53. package/hedhog/frontend/app/page.tsx.ejs +5 -5
  54. package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +1440 -1103
  55. package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +4 -3
  56. package/hedhog/frontend/app/person/_components/person-types.ts.ejs +14 -0
  57. package/hedhog/frontend/app/person/page.tsx.ejs +112 -190
  58. package/hedhog/frontend/app/pipeline/_components/lead-detail-sheet.tsx.ejs +599 -0
  59. package/hedhog/frontend/app/pipeline/page.tsx.ejs +1048 -299
  60. package/hedhog/frontend/app/reports/page.tsx.ejs +15 -15
  61. package/hedhog/frontend/messages/en.json +268 -0
  62. package/hedhog/frontend/messages/pt.json +233 -0
  63. package/package.json +6 -6
  64. package/src/contact.service.ts +2 -2
  65. package/src/person/dto/create-followup.dto.ts +15 -0
  66. package/src/person/dto/create-interaction.dto.ts +23 -0
  67. package/src/person/dto/create.dto.ts +50 -0
  68. package/src/person/dto/duplicates-query.dto.ts +34 -0
  69. package/src/person/dto/merge.dto.ts +15 -0
  70. package/src/person/dto/update-lifecycle-stage.dto.ts +19 -0
  71. package/src/person/dto/update.dto.ts +31 -1
  72. package/src/person/person.controller.ts +63 -2
  73. 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: 'PATCH',
124
- data: { next_action_at: values.next_action_at },
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,14 @@
1
1
  'use client';
2
2
 
3
- import { CrmNav } from '../_components/crm-nav';
4
3
  import { CopyButton } from '@/components/copy-button';
5
- import { Page, PageHeader } from '@/components/entity-list';
4
+ import {
5
+ EmptyState,
6
+ Page,
7
+ PageHeader,
8
+ PaginationFooter,
9
+ SearchBar,
10
+ type SearchBarControl,
11
+ } from '@/components/entity-list';
6
12
  import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
7
13
  import { Badge } from '@/components/ui/badge';
8
14
  import { Button } from '@/components/ui/button';
@@ -14,14 +20,7 @@ import {
14
20
  DropdownMenuSeparator,
15
21
  DropdownMenuTrigger,
16
22
  } 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';
23
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
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,78 @@ export default function PeoplePage() {
718
712
 
719
713
  const peopleRows = table.getRowModel().rows;
720
714
 
715
+ const statsCards = [
716
+ {
717
+ key: 'total',
718
+ title: t('statsTotal'),
719
+ value: stats.total,
720
+ icon: Users,
721
+ accentClassName: 'from-slate-500/20 via-slate-400/10 to-transparent',
722
+ iconContainerClassName: 'bg-slate-100 text-slate-700',
723
+ },
724
+ {
725
+ key: 'active',
726
+ title: t('statsActive'),
727
+ value: stats.active,
728
+ icon: CheckCircle2,
729
+ accentClassName: 'from-green-500/20 via-emerald-500/10 to-transparent',
730
+ iconContainerClassName: 'bg-green-50 text-green-600',
731
+ },
732
+ {
733
+ key: 'individual',
734
+ title: t('statsIndividual'),
735
+ value: stats.individual,
736
+ icon: UserCheck,
737
+ accentClassName: 'from-blue-500/20 via-cyan-500/10 to-transparent',
738
+ iconContainerClassName: 'bg-blue-50 text-blue-600',
739
+ },
740
+ {
741
+ key: 'company',
742
+ title: t('statsCompany'),
743
+ value: stats.company,
744
+ icon: Building2,
745
+ accentClassName: 'from-amber-500/20 via-orange-500/10 to-transparent',
746
+ iconContainerClassName: 'bg-amber-50 text-amber-600',
747
+ },
748
+ ];
749
+
750
+ const searchControls: SearchBarControl[] = [
751
+ ...(allowCompanyRegistration
752
+ ? [
753
+ {
754
+ id: 'type-filter',
755
+ type: 'select' as const,
756
+ value: typeFilter,
757
+ onChange: (value: string) => {
758
+ setTypeFilter(value);
759
+ setPage(1);
760
+ },
761
+ placeholder: t('filterByType'),
762
+ options: [
763
+ { value: 'all', label: t('allTypes') },
764
+ { value: 'individual', label: t('individual') },
765
+ { value: 'company', label: t('company') },
766
+ ],
767
+ },
768
+ ]
769
+ : []),
770
+ {
771
+ id: 'status-filter',
772
+ type: 'select',
773
+ value: statusFilter,
774
+ onChange: (value: string) => {
775
+ setStatusFilter(value);
776
+ setPage(1);
777
+ },
778
+ placeholder: t('filterByStatus'),
779
+ options: [
780
+ { value: 'all', label: t('allStatuses') },
781
+ { value: 'active', label: t('active') },
782
+ { value: 'inactive', label: t('inactive') },
783
+ ],
784
+ },
785
+ ];
786
+
721
787
  return (
722
788
  <Page>
723
789
  <PageHeader
@@ -745,104 +811,26 @@ export default function PeoplePage() {
745
811
  ]}
746
812
  />
747
813
 
748
- <CrmNav currentHref="/contact/person" />
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>
814
+ <KpiCardsGrid items={statsCards} />
794
815
 
795
816
  <div className="flex flex-col gap-4 xl:flex-row xl:items-center">
796
- <div className="relative flex-1">
797
- <Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
798
- <Input
799
- value={searchInput}
800
- onChange={(event) => {
801
- setSearchInput(event.target.value);
817
+ <div className="flex-1">
818
+ <SearchBar
819
+ searchQuery={searchInput}
820
+ onSearchChange={(value) => {
821
+ setSearchInput(value);
802
822
  setPage(1);
803
823
  }}
824
+ onSearch={() => {
825
+ setPage(1);
826
+ void refetch();
827
+ }}
804
828
  placeholder={t('searchByNamePlaceholder')}
805
- className="pl-9"
829
+ controls={searchControls}
806
830
  />
807
831
  </div>
808
832
 
809
833
  <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
834
  <div className="flex items-center justify-between gap-3 sm:justify-start">
847
835
  <span className="text-xs font-medium text-muted-foreground">
848
836
  {t('viewMode')}
@@ -908,17 +896,14 @@ export default function PeoplePage() {
908
896
  </div>
909
897
  )
910
898
  ) : paginate.data.length === 0 ? (
911
- <div className="flex flex-col items-center justify-center py-16 text-center">
912
- <Users className="mb-4 h-12 w-12 text-muted-foreground/50" />
913
- <h3 className="text-lg font-medium">{t('emptyStateTitle')}</h3>
914
- <p className="mb-4 max-w-sm text-sm text-muted-foreground">
915
- {t('emptyStateDescription')}
916
- </p>
917
- <Button onClick={openCreateSheet}>
918
- <Plus className="mr-2 h-4 w-4" />
919
- {t('newPerson')}
920
- </Button>
921
- </div>
899
+ <EmptyState
900
+ icon={<Users className="h-12 w-12" />}
901
+ title={t('emptyStateTitle')}
902
+ description={t('emptyStateDescription')}
903
+ actionLabel={t('newPerson')}
904
+ actionIcon={<Plus className="mr-2 h-4 w-4" />}
905
+ onAction={openCreateSheet}
906
+ />
922
907
  ) : (
923
908
  <>
924
909
  {viewMode === 'table' ? (
@@ -1230,81 +1215,18 @@ export default function PeoplePage() {
1230
1215
  </div>
1231
1216
  )}
1232
1217
 
1233
- <div className="flex flex-col gap-4 border-t p-4 lg:flex-row lg:items-center lg:justify-between">
1234
- <div className="text-sm text-muted-foreground">
1235
- {t('resultsCount', { count: paginate.total })}
1236
- </div>
1237
-
1238
- <div className="flex flex-col gap-3 sm:flex-row sm:items-center">
1239
- <div className="flex items-center gap-2">
1240
- <span className="text-sm text-muted-foreground">
1241
- {t('rowsPerPage')}
1242
- </span>
1243
- <Select
1244
- value={String(pageSize)}
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>
1218
+ <div className="border-t p-4">
1219
+ <PaginationFooter
1220
+ currentPage={page}
1221
+ pageSize={pageSize}
1222
+ totalItems={paginate.total}
1223
+ onPageChange={setPage}
1224
+ onPageSizeChange={(nextPageSize) => {
1225
+ setPageSize(nextPageSize);
1226
+ setPage(1);
1227
+ }}
1228
+ pageSizeOptions={[12, 20, 30, 40, 50]}
1229
+ />
1308
1230
  </div>
1309
1231
  </>
1310
1232
  )}