@hed-hog/contact 0.0.304 → 0.0.305
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 +225 -17
- package/dist/person/dto/account.dto.d.ts +5 -0
- package/dist/person/dto/account.dto.d.ts.map +1 -1
- package/dist/person/dto/account.dto.js +29 -0
- package/dist/person/dto/account.dto.js.map +1 -1
- package/dist/person/dto/import-preview.dto.d.ts +7 -0
- package/dist/person/dto/import-preview.dto.d.ts.map +1 -0
- package/dist/person/dto/import-preview.dto.js +7 -0
- package/dist/person/dto/import-preview.dto.js.map +1 -0
- package/dist/person/dto/import.dto.d.ts +15 -0
- package/dist/person/dto/import.dto.d.ts.map +1 -0
- package/dist/person/dto/import.dto.js +51 -0
- package/dist/person/dto/import.dto.js.map +1 -0
- package/dist/person/person.controller.d.ts +14 -0
- package/dist/person/person.controller.d.ts.map +1 -1
- package/dist/person/person.controller.js +53 -0
- package/dist/person/person.controller.js.map +1 -1
- package/dist/person/person.service.d.ts +19 -0
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +481 -67
- package/dist/person/person.service.js.map +1 -1
- package/dist/person-relation-type/person-relation-type.controller.d.ts +2 -2
- package/dist/person-relation-type/person-relation-type.service.d.ts +2 -2
- package/hedhog/data/route.yaml +6 -0
- package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +2242 -484
- package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +51 -0
- package/hedhog/frontend/app/accounts/page.tsx.ejs +181 -16
- package/hedhog/frontend/app/contact-type/page.tsx.ejs +223 -29
- package/hedhog/frontend/app/document-type/page.tsx.ejs +248 -37
- package/hedhog/frontend/app/follow-ups/page.tsx.ejs +129 -19
- package/hedhog/frontend/app/person/_components/person-field-with-create.tsx.ejs +78 -212
- package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +760 -178
- package/hedhog/frontend/app/person/_components/person-import-sheet.tsx.ejs +1120 -0
- package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +171 -4
- package/hedhog/frontend/app/person/page.tsx.ejs +17 -0
- package/hedhog/frontend/app/pipeline/_components/lead-proposals-tab.tsx.ejs +160 -35
- package/hedhog/frontend/messages/en.json +104 -2
- package/hedhog/frontend/messages/pt.json +111 -9
- package/package.json +5 -5
- package/src/person/dto/account.dto.ts +31 -0
- package/src/person/dto/import-preview.dto.ts +6 -0
- package/src/person/dto/import.dto.ts +61 -0
- package/src/person/person.controller.ts +74 -12
- package/src/person/person.service.ts +615 -68
|
@@ -6,6 +6,15 @@ export type AccountLifecycleStage =
|
|
|
6
6
|
| 'churned'
|
|
7
7
|
| 'inactive';
|
|
8
8
|
|
|
9
|
+
export type AddressTypeValue =
|
|
10
|
+
| 'residential'
|
|
11
|
+
| 'commercial'
|
|
12
|
+
| 'correspondence'
|
|
13
|
+
| 'alternative'
|
|
14
|
+
| 'work'
|
|
15
|
+
| 'billing'
|
|
16
|
+
| 'shipping';
|
|
17
|
+
|
|
9
18
|
export type UserOption = {
|
|
10
19
|
id: number;
|
|
11
20
|
name: string;
|
|
@@ -18,6 +27,41 @@ export type AccountStats = {
|
|
|
18
27
|
prospects: number;
|
|
19
28
|
};
|
|
20
29
|
|
|
30
|
+
export type AccountContact = {
|
|
31
|
+
id?: number;
|
|
32
|
+
value: string;
|
|
33
|
+
is_primary: boolean;
|
|
34
|
+
contact_type_id: number;
|
|
35
|
+
contact_type?: {
|
|
36
|
+
id: number;
|
|
37
|
+
code: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type AccountAddress = {
|
|
43
|
+
id?: number;
|
|
44
|
+
line1: string;
|
|
45
|
+
line2?: string;
|
|
46
|
+
city: string;
|
|
47
|
+
state: string;
|
|
48
|
+
is_primary: boolean;
|
|
49
|
+
address_type: AddressTypeValue;
|
|
50
|
+
postal_code?: string;
|
|
51
|
+
country_code?: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type AccountDocument = {
|
|
55
|
+
id?: number;
|
|
56
|
+
value: string;
|
|
57
|
+
document_type_id: number;
|
|
58
|
+
document_type?: {
|
|
59
|
+
id: number;
|
|
60
|
+
code: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
21
65
|
export type Account = {
|
|
22
66
|
id: number;
|
|
23
67
|
name: string;
|
|
@@ -36,6 +80,9 @@ export type Account = {
|
|
|
36
80
|
state?: string | null;
|
|
37
81
|
created_at: string;
|
|
38
82
|
last_interaction_at?: string | null;
|
|
83
|
+
contact?: AccountContact[];
|
|
84
|
+
address?: AccountAddress[];
|
|
85
|
+
document?: AccountDocument[];
|
|
39
86
|
};
|
|
40
87
|
|
|
41
88
|
export type PaginatedResult<T> = {
|
|
@@ -62,4 +109,8 @@ export type AccountFormValues = {
|
|
|
62
109
|
lifecycle_stage?: AccountLifecycleStage | null;
|
|
63
110
|
city?: string | null;
|
|
64
111
|
state?: string | null;
|
|
112
|
+
collaborator_person_ids?: number[];
|
|
113
|
+
contacts?: AccountContact[];
|
|
114
|
+
addresses?: AccountAddress[];
|
|
115
|
+
documents?: AccountDocument[];
|
|
65
116
|
};
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
TableRow,
|
|
40
40
|
} from '@/components/ui/table';
|
|
41
41
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
42
|
+
import { getFormDraftOwnerKey } from '@/hooks/use-form-draft';
|
|
42
43
|
import { formatDate } from '@/lib/format-date';
|
|
43
44
|
import { cn } from '@/lib/utils';
|
|
44
45
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
@@ -61,10 +62,16 @@ import {
|
|
|
61
62
|
Phone,
|
|
62
63
|
Plus,
|
|
63
64
|
Trash2,
|
|
65
|
+
Upload,
|
|
64
66
|
} from 'lucide-react';
|
|
65
67
|
import { useTranslations } from 'next-intl';
|
|
66
68
|
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
|
67
69
|
import { toast } from 'sonner';
|
|
70
|
+
import { PersonImportSheet } from '../person/_components/person-import-sheet';
|
|
71
|
+
import type {
|
|
72
|
+
ContactTypeOption,
|
|
73
|
+
DocumentTypeOption,
|
|
74
|
+
} from '../person/_components/person-types';
|
|
68
75
|
import { AccountFormSheet } from './_components/account-form-sheet';
|
|
69
76
|
import type {
|
|
70
77
|
Account,
|
|
@@ -75,6 +82,7 @@ import type {
|
|
|
75
82
|
} from './_components/account-types';
|
|
76
83
|
|
|
77
84
|
const ACCOUNT_VIEW_STORAGE_KEY = 'contact-account-view-mode';
|
|
85
|
+
const ACCOUNT_FORM_DRAFT_STORAGE_KEY = 'contact-account-form-draft';
|
|
78
86
|
|
|
79
87
|
type AccountViewMode = 'table' | 'cards';
|
|
80
88
|
|
|
@@ -150,7 +158,8 @@ function AccountInfoTile({
|
|
|
150
158
|
export default function AccountsPage() {
|
|
151
159
|
const t = useTranslations('contact.AccountsPage');
|
|
152
160
|
const crmT = useTranslations('contact.CrmMenu');
|
|
153
|
-
const { request, currentLocaleCode, getSettingValue } =
|
|
161
|
+
const { request, currentLocaleCode, getSettingValue, accessToken, user } =
|
|
162
|
+
useApp();
|
|
154
163
|
|
|
155
164
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
156
165
|
const [page, setPage] = useState(1);
|
|
@@ -166,6 +175,13 @@ export default function AccountsPage() {
|
|
|
166
175
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
167
176
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
168
177
|
const [viewMode, setViewMode] = useState<AccountViewMode>('table');
|
|
178
|
+
const [importSheetOpen, setImportSheetOpen] = useState(false);
|
|
179
|
+
const [importAccountId, setImportAccountId] = useState<number | null>(null);
|
|
180
|
+
|
|
181
|
+
const openImportSheet = (account: Account) => {
|
|
182
|
+
setImportAccountId(account.id);
|
|
183
|
+
setImportSheetOpen(true);
|
|
184
|
+
};
|
|
169
185
|
|
|
170
186
|
useEffect(() => {
|
|
171
187
|
const timeout = setTimeout(() => {
|
|
@@ -188,6 +204,47 @@ export default function AccountsPage() {
|
|
|
188
204
|
}
|
|
189
205
|
}, []);
|
|
190
206
|
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const ownerKey = getFormDraftOwnerKey(
|
|
209
|
+
accessToken,
|
|
210
|
+
user?.id ?? (user === null ? null : undefined)
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (accessToken && !ownerKey) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const rawDraft = window.localStorage.getItem(
|
|
219
|
+
ACCOUNT_FORM_DRAFT_STORAGE_KEY
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (!rawDraft) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const parsedDraft = JSON.parse(rawDraft) as {
|
|
227
|
+
mode?: string;
|
|
228
|
+
ownerKey?: string | null;
|
|
229
|
+
} | null;
|
|
230
|
+
|
|
231
|
+
if (accessToken) {
|
|
232
|
+
if (!parsedDraft?.ownerKey || parsedDraft.ownerKey !== ownerKey) {
|
|
233
|
+
window.localStorage.removeItem(ACCOUNT_FORM_DRAFT_STORAGE_KEY);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
} else if (parsedDraft?.ownerKey && parsedDraft.ownerKey !== ownerKey) {
|
|
237
|
+
window.localStorage.removeItem(ACCOUNT_FORM_DRAFT_STORAGE_KEY);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
setAccountToEdit(null);
|
|
242
|
+
setFormSheetOpen(true);
|
|
243
|
+
} catch {
|
|
244
|
+
// Ignore draft restore failures.
|
|
245
|
+
}
|
|
246
|
+
}, [accessToken, user]);
|
|
247
|
+
|
|
191
248
|
const currentSort = sorting[0];
|
|
192
249
|
const sortField = currentSort?.id === 'created_at' ? 'created_at' : 'name';
|
|
193
250
|
const sortOrder = currentSort?.desc ? 'desc' : 'asc';
|
|
@@ -234,6 +291,32 @@ export default function AccountsPage() {
|
|
|
234
291
|
placeholderData: (previous) => previous ?? [],
|
|
235
292
|
});
|
|
236
293
|
|
|
294
|
+
const { data: contactTypes = [] } = useQuery<ContactTypeOption[]>({
|
|
295
|
+
queryKey: ['contact-account-contact-types', currentLocaleCode],
|
|
296
|
+
queryFn: async () => {
|
|
297
|
+
const response = await request<{ data: ContactTypeOption[] }>({
|
|
298
|
+
url: '/person-contact-type?pageSize=100',
|
|
299
|
+
method: 'GET',
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return response.data.data || [];
|
|
303
|
+
},
|
|
304
|
+
placeholderData: (previous) => previous ?? [],
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const { data: documentTypes = [] } = useQuery<DocumentTypeOption[]>({
|
|
308
|
+
queryKey: ['contact-account-document-types', currentLocaleCode],
|
|
309
|
+
queryFn: async () => {
|
|
310
|
+
const response = await request<{ data: DocumentTypeOption[] }>({
|
|
311
|
+
url: '/person-document-type?pageSize=100',
|
|
312
|
+
method: 'GET',
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return response.data.data || [];
|
|
316
|
+
},
|
|
317
|
+
placeholderData: (previous) => previous ?? [],
|
|
318
|
+
});
|
|
319
|
+
|
|
237
320
|
const {
|
|
238
321
|
data: paginate = {
|
|
239
322
|
data: [],
|
|
@@ -314,29 +397,89 @@ export default function AccountsPage() {
|
|
|
314
397
|
setFormSheetOpen(true);
|
|
315
398
|
};
|
|
316
399
|
|
|
317
|
-
const handleFormSubmit = async (
|
|
400
|
+
const handleFormSubmit = async (
|
|
401
|
+
data: AccountFormValues,
|
|
402
|
+
accountId?: number | null
|
|
403
|
+
): Promise<{ id: number | null }> => {
|
|
318
404
|
try {
|
|
319
405
|
setIsSubmitting(true);
|
|
320
406
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
407
|
+
const targetAccountId = accountToEdit?.id ?? accountId ?? null;
|
|
408
|
+
|
|
409
|
+
if (targetAccountId) {
|
|
410
|
+
const response = await request<{ id?: number; data?: { id?: number } }>(
|
|
411
|
+
{
|
|
412
|
+
url: `/person/accounts/${targetAccountId}`,
|
|
413
|
+
method: 'PATCH',
|
|
414
|
+
data,
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const savedId = Number(
|
|
419
|
+
response?.data?.id ?? response?.data?.data?.id ?? targetAccountId
|
|
420
|
+
);
|
|
421
|
+
|
|
327
422
|
toast.success(t('editSuccess'));
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
423
|
+
|
|
424
|
+
setAccountToEdit((previous) =>
|
|
425
|
+
previous
|
|
426
|
+
? {
|
|
427
|
+
...previous,
|
|
428
|
+
...data,
|
|
429
|
+
id: savedId,
|
|
430
|
+
owner_user:
|
|
431
|
+
owners.find((owner) => owner.id === data.owner_user_id) ??
|
|
432
|
+
previous.owner_user ??
|
|
433
|
+
null,
|
|
434
|
+
contact: data.contacts ?? previous.contact ?? [],
|
|
435
|
+
address: data.addresses ?? previous.address ?? [],
|
|
436
|
+
document: data.documents ?? previous.document ?? [],
|
|
437
|
+
}
|
|
438
|
+
: previous
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
await Promise.all([refetchAccounts(), refetchStats()]);
|
|
442
|
+
return { id: savedId || null };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const response = await request<{ id?: number; data?: { id?: number } }>({
|
|
446
|
+
url: '/person/accounts',
|
|
447
|
+
method: 'POST',
|
|
448
|
+
data,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const savedId = Number(response?.data?.id ?? response?.data?.data?.id);
|
|
452
|
+
|
|
453
|
+
toast.success(t('createSuccess'));
|
|
454
|
+
|
|
455
|
+
if (savedId) {
|
|
456
|
+
setAccountToEdit({
|
|
457
|
+
id: savedId,
|
|
458
|
+
name: data.name,
|
|
459
|
+
trade_name: data.trade_name ?? null,
|
|
460
|
+
status: data.status,
|
|
461
|
+
industry: data.industry ?? null,
|
|
462
|
+
website: data.website ?? null,
|
|
463
|
+
email: data.email ?? null,
|
|
464
|
+
phone: data.phone ?? null,
|
|
465
|
+
owner_user_id: data.owner_user_id ?? null,
|
|
466
|
+
owner_user:
|
|
467
|
+
owners.find((owner) => owner.id === data.owner_user_id) ?? null,
|
|
468
|
+
annual_revenue: data.annual_revenue ?? null,
|
|
469
|
+
employee_count: data.employee_count ?? null,
|
|
470
|
+
lifecycle_stage: data.lifecycle_stage ?? null,
|
|
471
|
+
city: data.city ?? null,
|
|
472
|
+
state: data.state ?? null,
|
|
473
|
+
created_at: new Date().toISOString(),
|
|
474
|
+
last_interaction_at: null,
|
|
475
|
+
contact: data.contacts ?? [],
|
|
476
|
+
address: data.addresses ?? [],
|
|
477
|
+
document: data.documents ?? [],
|
|
333
478
|
});
|
|
334
|
-
toast.success(t('createSuccess'));
|
|
335
479
|
}
|
|
336
480
|
|
|
337
|
-
setFormSheetOpen(false);
|
|
338
|
-
setAccountToEdit(null);
|
|
339
481
|
await Promise.all([refetchAccounts(), refetchStats()]);
|
|
482
|
+
return { id: savedId || null };
|
|
340
483
|
} catch (error: unknown) {
|
|
341
484
|
const message =
|
|
342
485
|
typeof error === 'object' && error && 'message' in error
|
|
@@ -537,6 +680,10 @@ export default function AccountsPage() {
|
|
|
537
680
|
<Pencil className="mr-2 h-4 w-4" />
|
|
538
681
|
{t('edit')}
|
|
539
682
|
</DropdownMenuItem>
|
|
683
|
+
<DropdownMenuItem onClick={() => openImportSheet(account)}>
|
|
684
|
+
<Upload className="mr-2 h-4 w-4" />
|
|
685
|
+
{t('importContacts')}
|
|
686
|
+
</DropdownMenuItem>
|
|
540
687
|
<DropdownMenuSeparator />
|
|
541
688
|
<DropdownMenuItem
|
|
542
689
|
className="text-red-600"
|
|
@@ -867,6 +1014,12 @@ export default function AccountsPage() {
|
|
|
867
1014
|
<Pencil className="mr-2 h-4 w-4" />
|
|
868
1015
|
{t('edit')}
|
|
869
1016
|
</DropdownMenuItem>
|
|
1017
|
+
<DropdownMenuItem
|
|
1018
|
+
onClick={() => openImportSheet(account)}
|
|
1019
|
+
>
|
|
1020
|
+
<Upload className="mr-2 h-4 w-4" />
|
|
1021
|
+
{t('importContacts')}
|
|
1022
|
+
</DropdownMenuItem>
|
|
870
1023
|
<DropdownMenuSeparator />
|
|
871
1024
|
<DropdownMenuItem
|
|
872
1025
|
className="text-red-600"
|
|
@@ -937,10 +1090,22 @@ export default function AccountsPage() {
|
|
|
937
1090
|
onOpenChange={setFormSheetOpen}
|
|
938
1091
|
account={accountToEdit}
|
|
939
1092
|
owners={owners}
|
|
1093
|
+
contactTypes={contactTypes}
|
|
1094
|
+
documentTypes={documentTypes}
|
|
940
1095
|
onSubmit={handleFormSubmit}
|
|
941
1096
|
isLoading={isSubmitting}
|
|
942
1097
|
/>
|
|
943
1098
|
|
|
1099
|
+
<PersonImportSheet
|
|
1100
|
+
open={importSheetOpen}
|
|
1101
|
+
onOpenChange={(nextOpen) => {
|
|
1102
|
+
setImportSheetOpen(nextOpen);
|
|
1103
|
+
if (!nextOpen) setImportAccountId(null);
|
|
1104
|
+
}}
|
|
1105
|
+
initialCompanyId={importAccountId}
|
|
1106
|
+
onSuccess={() => setImportSheetOpen(false)}
|
|
1107
|
+
/>
|
|
1108
|
+
|
|
944
1109
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
945
1110
|
<AlertDialogContent>
|
|
946
1111
|
<AlertDialogHeader>
|