@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.
Files changed (44) hide show
  1. package/README.md +225 -17
  2. package/dist/person/dto/account.dto.d.ts +5 -0
  3. package/dist/person/dto/account.dto.d.ts.map +1 -1
  4. package/dist/person/dto/account.dto.js +29 -0
  5. package/dist/person/dto/account.dto.js.map +1 -1
  6. package/dist/person/dto/import-preview.dto.d.ts +7 -0
  7. package/dist/person/dto/import-preview.dto.d.ts.map +1 -0
  8. package/dist/person/dto/import-preview.dto.js +7 -0
  9. package/dist/person/dto/import-preview.dto.js.map +1 -0
  10. package/dist/person/dto/import.dto.d.ts +15 -0
  11. package/dist/person/dto/import.dto.d.ts.map +1 -0
  12. package/dist/person/dto/import.dto.js +51 -0
  13. package/dist/person/dto/import.dto.js.map +1 -0
  14. package/dist/person/person.controller.d.ts +14 -0
  15. package/dist/person/person.controller.d.ts.map +1 -1
  16. package/dist/person/person.controller.js +53 -0
  17. package/dist/person/person.controller.js.map +1 -1
  18. package/dist/person/person.service.d.ts +19 -0
  19. package/dist/person/person.service.d.ts.map +1 -1
  20. package/dist/person/person.service.js +481 -67
  21. package/dist/person/person.service.js.map +1 -1
  22. package/dist/person-relation-type/person-relation-type.controller.d.ts +2 -2
  23. package/dist/person-relation-type/person-relation-type.service.d.ts +2 -2
  24. package/hedhog/data/route.yaml +6 -0
  25. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +2242 -484
  26. package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +51 -0
  27. package/hedhog/frontend/app/accounts/page.tsx.ejs +181 -16
  28. package/hedhog/frontend/app/contact-type/page.tsx.ejs +223 -29
  29. package/hedhog/frontend/app/document-type/page.tsx.ejs +248 -37
  30. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +129 -19
  31. package/hedhog/frontend/app/person/_components/person-field-with-create.tsx.ejs +78 -212
  32. package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +760 -178
  33. package/hedhog/frontend/app/person/_components/person-import-sheet.tsx.ejs +1120 -0
  34. package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +171 -4
  35. package/hedhog/frontend/app/person/page.tsx.ejs +17 -0
  36. package/hedhog/frontend/app/pipeline/_components/lead-proposals-tab.tsx.ejs +160 -35
  37. package/hedhog/frontend/messages/en.json +104 -2
  38. package/hedhog/frontend/messages/pt.json +111 -9
  39. package/package.json +5 -5
  40. package/src/person/dto/account.dto.ts +31 -0
  41. package/src/person/dto/import-preview.dto.ts +6 -0
  42. package/src/person/dto/import.dto.ts +61 -0
  43. package/src/person/person.controller.ts +74 -12
  44. package/src/person/person.service.ts +615 -68
@@ -18,14 +18,6 @@ import {
18
18
  AlertDialogTitle,
19
19
  } from '@/components/ui/alert-dialog';
20
20
  import { Button } from '@/components/ui/button';
21
- import {
22
- Sheet,
23
- SheetContent,
24
- SheetDescription,
25
- SheetFooter,
26
- SheetHeader,
27
- SheetTitle,
28
- } from '@/components/ui/sheet';
29
21
  import {
30
22
  DropdownMenu,
31
23
  DropdownMenuContent,
@@ -43,6 +35,14 @@ import {
43
35
  FormMessage,
44
36
  } from '@/components/ui/form';
45
37
  import { Input } from '@/components/ui/input';
38
+ import {
39
+ Sheet,
40
+ SheetContent,
41
+ SheetDescription,
42
+ SheetFooter,
43
+ SheetHeader,
44
+ SheetTitle,
45
+ } from '@/components/ui/sheet';
46
46
  import {
47
47
  Table,
48
48
  TableBody,
@@ -51,13 +51,16 @@ import {
51
51
  TableHeader,
52
52
  TableRow,
53
53
  } from '@/components/ui/table';
54
- import { formatDate } from '@/lib/format-date';
54
+ import { useFormDraft } from '@/hooks/use-form-draft';
55
+ import { formatDate, formatDateTime } from '@/lib/format-date';
55
56
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
56
57
  import { zodResolver } from '@hookform/resolvers/zod';
58
+ import { formatDistanceToNow } from 'date-fns';
59
+ import { enUS, ptBR } from 'date-fns/locale';
57
60
  import { MoreHorizontal, Pencil, Plus, Trash2 } from 'lucide-react';
58
61
  import { useTranslations } from 'next-intl';
59
- import { useState } from 'react';
60
- import { useForm } from 'react-hook-form';
62
+ import { useMemo, useState } from 'react';
63
+ import { useForm, useWatch } from 'react-hook-form';
61
64
  import { toast } from 'sonner';
62
65
  import { z } from 'zod';
63
66
 
@@ -76,6 +79,37 @@ type ContactType = {
76
79
  created_at: string;
77
80
  };
78
81
 
82
+ type ContactTypeDraftPayload = {
83
+ mode: 'create' | 'edit';
84
+ contactTypeId: number | null;
85
+ values: {
86
+ code: string;
87
+ name: string;
88
+ };
89
+ };
90
+
91
+ type ContactTypeResponse = {
92
+ code?: string;
93
+ name?: string;
94
+ };
95
+
96
+ function getErrorMessage(error: unknown, fallbackMessage: string) {
97
+ if (
98
+ error &&
99
+ typeof error === 'object' &&
100
+ 'message' in error &&
101
+ typeof error.message === 'string'
102
+ ) {
103
+ return error.message;
104
+ }
105
+
106
+ return fallbackMessage;
107
+ }
108
+
109
+ const CONTACT_TYPE_CREATE_DRAFT_STORAGE_KEY =
110
+ 'contact-contact-type-create-draft';
111
+ const CONTACT_TYPE_EDIT_DRAFT_STORAGE_KEY = 'contact-contact-type-edit-draft';
112
+
79
113
  export default function ContactTypePage() {
80
114
  const t = useTranslations('contact.ContactType');
81
115
 
@@ -129,6 +163,124 @@ export default function ContactTypePage() {
129
163
  resolver: zodResolver(contactTypeSchema),
130
164
  });
131
165
 
166
+ const watchedCreateValues = useWatch({
167
+ control: form.control,
168
+ });
169
+ const watchedEditValues = useWatch({
170
+ control: editForm.control,
171
+ });
172
+
173
+ const {
174
+ clearDraft: clearCreateDraft,
175
+ loadDraft: loadCreateDraft,
176
+ hasDraft: hasCreateDraft,
177
+ savedAt: createDraftSavedAt,
178
+ } = useFormDraft<ContactTypeDraftPayload>({
179
+ storageKey: CONTACT_TYPE_CREATE_DRAFT_STORAGE_KEY,
180
+ value: {
181
+ mode: 'create',
182
+ contactTypeId: null,
183
+ values: {
184
+ code: watchedCreateValues.code ?? '',
185
+ name: watchedCreateValues.name ?? '',
186
+ },
187
+ },
188
+ hasData: Boolean(
189
+ (watchedCreateValues.code ?? '').trim() ||
190
+ (watchedCreateValues.name ?? '').trim()
191
+ ),
192
+ enabled: isDialogOpen,
193
+ });
194
+
195
+ const {
196
+ clearDraft: clearEditDraft,
197
+ loadDraft: loadEditDraft,
198
+ hasDraft: hasEditDraft,
199
+ savedAt: editDraftSavedAt,
200
+ } = useFormDraft<ContactTypeDraftPayload>({
201
+ storageKey: CONTACT_TYPE_EDIT_DRAFT_STORAGE_KEY,
202
+ value: {
203
+ mode: 'edit',
204
+ contactTypeId:
205
+ editingContactType?.contact_type_id || editingContactType?.id || null,
206
+ values: {
207
+ code: watchedEditValues.code ?? '',
208
+ name: watchedEditValues.name ?? '',
209
+ },
210
+ },
211
+ hasData: Boolean(
212
+ (watchedEditValues.code ?? '').trim() ||
213
+ (watchedEditValues.name ?? '').trim()
214
+ ),
215
+ enabled: isEditDialogOpen,
216
+ });
217
+
218
+ const createDraftStatusContent = useMemo(() => {
219
+ if (!hasCreateDraft || !createDraftSavedAt) {
220
+ return null;
221
+ }
222
+
223
+ const savedDate = new Date(createDraftSavedAt);
224
+ if (Number.isNaN(savedDate.getTime())) {
225
+ return null;
226
+ }
227
+
228
+ const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
229
+ const relativeLabel = formatDistanceToNow(savedDate, {
230
+ addSuffix: true,
231
+ locale,
232
+ });
233
+ const absoluteLabel = formatDateTime(
234
+ savedDate,
235
+ getSettingValue,
236
+ currentLocaleCode
237
+ );
238
+
239
+ return currentLocaleCode.startsWith('pt')
240
+ ? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
241
+ : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
242
+ }, [createDraftSavedAt, currentLocaleCode, getSettingValue, hasCreateDraft]);
243
+
244
+ const editDraftStatusContent = useMemo(() => {
245
+ if (!hasEditDraft || !editDraftSavedAt) {
246
+ return null;
247
+ }
248
+
249
+ const savedDate = new Date(editDraftSavedAt);
250
+ if (Number.isNaN(savedDate.getTime())) {
251
+ return null;
252
+ }
253
+
254
+ const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
255
+ const relativeLabel = formatDistanceToNow(savedDate, {
256
+ addSuffix: true,
257
+ locale,
258
+ });
259
+ const absoluteLabel = formatDateTime(
260
+ savedDate,
261
+ getSettingValue,
262
+ currentLocaleCode
263
+ );
264
+
265
+ return currentLocaleCode.startsWith('pt')
266
+ ? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
267
+ : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
268
+ }, [editDraftSavedAt, currentLocaleCode, getSettingValue, hasEditDraft]);
269
+
270
+ const openCreateDialog = () => {
271
+ const storedDraft = loadCreateDraft();
272
+
273
+ form.reset(
274
+ storedDraft?.payload.mode === 'create'
275
+ ? storedDraft.payload.values
276
+ : {
277
+ code: '',
278
+ name: '',
279
+ }
280
+ );
281
+ setIsDialogOpen(true);
282
+ };
283
+
132
284
  const onSubmit = async (values: z.infer<typeof contactTypeSchema>) => {
133
285
  const payload = {
134
286
  code: values.code,
@@ -146,12 +298,13 @@ export default function ContactTypePage() {
146
298
  data: payload,
147
299
  });
148
300
 
301
+ clearCreateDraft();
149
302
  toast.success(t('successCreate'));
150
303
  setIsDialogOpen(false);
151
304
  form.reset();
152
305
  refetch();
153
- } catch (error: any) {
154
- toast.error(error?.message || t('errorCreate'));
306
+ } catch (error: unknown) {
307
+ toast.error(getErrorMessage(error, t('errorCreate')));
155
308
  }
156
309
  };
157
310
 
@@ -174,13 +327,14 @@ export default function ContactTypePage() {
174
327
  data: payload,
175
328
  });
176
329
 
330
+ clearEditDraft();
177
331
  toast.success(t('successUpdate'));
178
332
  setIsEditDialogOpen(false);
179
333
  setEditingContactType(null);
180
334
  editForm.reset();
181
335
  refetch();
182
- } catch (error: any) {
183
- toast.error(error?.message || t('errorUpdate'));
336
+ } catch (error: unknown) {
337
+ toast.error(getErrorMessage(error, t('errorUpdate')));
184
338
  }
185
339
  };
186
340
 
@@ -196,8 +350,8 @@ export default function ContactTypePage() {
196
350
  setDeleteDialogOpen(false);
197
351
  setDeletingId(null);
198
352
  refetch();
199
- } catch (error: any) {
200
- toast.error(error?.message || t('errorDelete'));
353
+ } catch (error: unknown) {
354
+ toast.error(getErrorMessage(error, t('errorDelete')));
201
355
  }
202
356
  };
203
357
 
@@ -205,19 +359,39 @@ export default function ContactTypePage() {
205
359
  (async () => {
206
360
  setEditingContactType(contactType);
207
361
  try {
208
- const { data } = await request<any>({
362
+ const { data } = await request<ContactTypeResponse>({
209
363
  url: `/person-contact-type/${contactType.contact_type_id || contactType.id}?locale=${currentLocaleCode}`,
210
364
  method: 'GET',
211
365
  });
212
- editForm.reset({
213
- code: data.code || contactType.code,
214
- name: data.name || contactType.name || '',
215
- });
366
+ const storedDraft = loadEditDraft();
367
+ const shouldRestoreDraft =
368
+ storedDraft?.payload.mode === 'edit' &&
369
+ storedDraft.payload.contactTypeId ===
370
+ (contactType.contact_type_id || contactType.id);
371
+
372
+ editForm.reset(
373
+ shouldRestoreDraft
374
+ ? storedDraft.payload.values
375
+ : {
376
+ code: data.code || contactType.code,
377
+ name: data.name || contactType.name || '',
378
+ }
379
+ );
216
380
  } catch {
217
- editForm.reset({
218
- code: contactType.code,
219
- name: contactType.name || '',
220
- });
381
+ const storedDraft = loadEditDraft();
382
+ const shouldRestoreDraft =
383
+ storedDraft?.payload.mode === 'edit' &&
384
+ storedDraft.payload.contactTypeId ===
385
+ (contactType.contact_type_id || contactType.id);
386
+
387
+ editForm.reset(
388
+ shouldRestoreDraft
389
+ ? storedDraft.payload.values
390
+ : {
391
+ code: contactType.code,
392
+ name: contactType.name || '',
393
+ }
394
+ );
221
395
  }
222
396
  setIsEditDialogOpen(true);
223
397
  })();
@@ -235,7 +409,7 @@ export default function ContactTypePage() {
235
409
  actions={[
236
410
  {
237
411
  label: t('buttonNewType'),
238
- onClick: () => setIsDialogOpen(true),
412
+ onClick: openCreateDialog,
239
413
  variant: 'default',
240
414
  icon: <Plus />,
241
415
  },
@@ -332,7 +506,7 @@ export default function ContactTypePage() {
332
506
  title={t('noResults')}
333
507
  description={t('emptyStateDescription')}
334
508
  actionLabel={t('emptyStateAction')}
335
- onAction={() => setIsDialogOpen(true)}
509
+ onAction={openCreateDialog}
336
510
  />
337
511
  </div>
338
512
  )}
@@ -346,7 +520,15 @@ export default function ContactTypePage() {
346
520
  pageSizeOptions={[10, 20, 30, 40, 50]}
347
521
  />
348
522
 
349
- <Sheet open={isDialogOpen} onOpenChange={setIsDialogOpen}>
523
+ <Sheet
524
+ open={isDialogOpen}
525
+ onOpenChange={(open) => {
526
+ setIsDialogOpen(open);
527
+ if (!open) {
528
+ form.reset();
529
+ }
530
+ }}
531
+ >
350
532
  <SheetContent className="w-full sm:max-w-md">
351
533
  <SheetHeader>
352
534
  <SheetTitle>{t('dialogNewTitle')}</SheetTitle>
@@ -392,6 +574,12 @@ export default function ContactTypePage() {
392
574
  )}
393
575
  />
394
576
 
577
+ {createDraftStatusContent ? (
578
+ <p className="text-xs text-muted-foreground">
579
+ {createDraftStatusContent}
580
+ </p>
581
+ ) : null}
582
+
395
583
  <SheetFooter className="px-0">
396
584
  <Button type="submit">{t('buttonCreate')}</Button>
397
585
  </SheetFooter>
@@ -455,6 +643,12 @@ export default function ContactTypePage() {
455
643
  )}
456
644
  />
457
645
 
646
+ {editDraftStatusContent ? (
647
+ <p className="text-xs text-muted-foreground">
648
+ {editDraftStatusContent}
649
+ </p>
650
+ ) : null}
651
+
458
652
  <SheetFooter className="px-0">
459
653
  <Button type="submit">{t('buttonUpdate')}</Button>
460
654
  </SheetFooter>