@hed-hog/contact 0.0.304 → 0.0.306

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 +4 -4
  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,
@@ -50,6 +42,14 @@ import {
50
42
  SelectTrigger,
51
43
  SelectValue,
52
44
  } from '@/components/ui/select';
45
+ import {
46
+ Sheet,
47
+ SheetContent,
48
+ SheetDescription,
49
+ SheetFooter,
50
+ SheetHeader,
51
+ SheetTitle,
52
+ } from '@/components/ui/sheet';
53
53
  import { Switch } from '@/components/ui/switch';
54
54
  import {
55
55
  Table,
@@ -60,13 +60,16 @@ import {
60
60
  TableRow,
61
61
  } from '@/components/ui/table';
62
62
  import { COUNTRIES } from '@/constants/countries';
63
- import { formatDate } from '@/lib/format-date';
63
+ import { useFormDraft } from '@/hooks/use-form-draft';
64
+ import { formatDate, formatDateTime } from '@/lib/format-date';
64
65
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
65
66
  import { zodResolver } from '@hookform/resolvers/zod';
67
+ import { formatDistanceToNow } from 'date-fns';
68
+ import { enUS, ptBR } from 'date-fns/locale';
66
69
  import { MoreHorizontal, Pencil, Plus, Trash2 } from 'lucide-react';
67
70
  import { useTranslations } from 'next-intl';
68
- import { useState } from 'react';
69
- import { useForm } from 'react-hook-form';
71
+ import { useMemo, useState } from 'react';
72
+ import { useForm, useWatch } from 'react-hook-form';
70
73
  import { toast } from 'sonner';
71
74
  import { z } from 'zod';
72
75
 
@@ -87,6 +90,41 @@ type DocumentType = {
87
90
  created_at: string;
88
91
  };
89
92
 
93
+ type DocumentTypeDraftPayload = {
94
+ mode: 'create' | 'edit';
95
+ documentTypeId: number | null;
96
+ values: {
97
+ code: string;
98
+ name: string;
99
+ country_code: string;
100
+ is_unique: boolean;
101
+ };
102
+ };
103
+
104
+ type DocumentTypeResponse = {
105
+ code?: string;
106
+ name?: string;
107
+ country_code?: string;
108
+ is_unique?: boolean;
109
+ };
110
+
111
+ function getErrorMessage(error: unknown, fallbackMessage: string) {
112
+ if (
113
+ error &&
114
+ typeof error === 'object' &&
115
+ 'message' in error &&
116
+ typeof error.message === 'string'
117
+ ) {
118
+ return error.message;
119
+ }
120
+
121
+ return fallbackMessage;
122
+ }
123
+
124
+ const DOCUMENT_TYPE_CREATE_DRAFT_STORAGE_KEY =
125
+ 'contact-document-type-create-draft';
126
+ const DOCUMENT_TYPE_EDIT_DRAFT_STORAGE_KEY = 'contact-document-type-edit-draft';
127
+
90
128
  export default function DocumentTypePage() {
91
129
  const t = useTranslations('contact.DocumentType');
92
130
 
@@ -150,6 +188,136 @@ export default function DocumentTypePage() {
150
188
  resolver: zodResolver(documentTypeSchema),
151
189
  });
152
190
 
191
+ const watchedCreateValues = useWatch({
192
+ control: form.control,
193
+ });
194
+ const watchedEditValues = useWatch({
195
+ control: editForm.control,
196
+ });
197
+
198
+ const {
199
+ clearDraft: clearCreateDraft,
200
+ loadDraft: loadCreateDraft,
201
+ hasDraft: hasCreateDraft,
202
+ savedAt: createDraftSavedAt,
203
+ } = useFormDraft<DocumentTypeDraftPayload>({
204
+ storageKey: DOCUMENT_TYPE_CREATE_DRAFT_STORAGE_KEY,
205
+ value: {
206
+ mode: 'create',
207
+ documentTypeId: null,
208
+ values: {
209
+ code: watchedCreateValues.code ?? '',
210
+ name: watchedCreateValues.name ?? '',
211
+ country_code: watchedCreateValues.country_code ?? '',
212
+ is_unique: watchedCreateValues.is_unique ?? false,
213
+ },
214
+ },
215
+ hasData: Boolean(
216
+ (watchedCreateValues.code ?? '').trim() ||
217
+ (watchedCreateValues.name ?? '').trim() ||
218
+ (watchedCreateValues.country_code ?? '').trim() ||
219
+ watchedCreateValues.is_unique
220
+ ),
221
+ enabled: isDialogOpen,
222
+ });
223
+
224
+ const {
225
+ clearDraft: clearEditDraft,
226
+ loadDraft: loadEditDraft,
227
+ hasDraft: hasEditDraft,
228
+ savedAt: editDraftSavedAt,
229
+ } = useFormDraft<DocumentTypeDraftPayload>({
230
+ storageKey: DOCUMENT_TYPE_EDIT_DRAFT_STORAGE_KEY,
231
+ value: {
232
+ mode: 'edit',
233
+ documentTypeId:
234
+ editingDocumentType?.document_type_id ||
235
+ editingDocumentType?.id ||
236
+ null,
237
+ values: {
238
+ code: watchedEditValues.code ?? '',
239
+ name: watchedEditValues.name ?? '',
240
+ country_code: watchedEditValues.country_code ?? '',
241
+ is_unique: watchedEditValues.is_unique ?? false,
242
+ },
243
+ },
244
+ hasData: Boolean(
245
+ (watchedEditValues.code ?? '').trim() ||
246
+ (watchedEditValues.name ?? '').trim() ||
247
+ (watchedEditValues.country_code ?? '').trim() ||
248
+ watchedEditValues.is_unique
249
+ ),
250
+ enabled: isEditDialogOpen,
251
+ });
252
+
253
+ const createDraftStatusContent = useMemo(() => {
254
+ if (!hasCreateDraft || !createDraftSavedAt) {
255
+ return null;
256
+ }
257
+
258
+ const savedDate = new Date(createDraftSavedAt);
259
+ if (Number.isNaN(savedDate.getTime())) {
260
+ return null;
261
+ }
262
+
263
+ const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
264
+ const relativeLabel = formatDistanceToNow(savedDate, {
265
+ addSuffix: true,
266
+ locale,
267
+ });
268
+ const absoluteLabel = formatDateTime(
269
+ savedDate,
270
+ getSettingValue,
271
+ currentLocaleCode
272
+ );
273
+
274
+ return currentLocaleCode.startsWith('pt')
275
+ ? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
276
+ : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
277
+ }, [createDraftSavedAt, currentLocaleCode, getSettingValue, hasCreateDraft]);
278
+
279
+ const editDraftStatusContent = useMemo(() => {
280
+ if (!hasEditDraft || !editDraftSavedAt) {
281
+ return null;
282
+ }
283
+
284
+ const savedDate = new Date(editDraftSavedAt);
285
+ if (Number.isNaN(savedDate.getTime())) {
286
+ return null;
287
+ }
288
+
289
+ const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
290
+ const relativeLabel = formatDistanceToNow(savedDate, {
291
+ addSuffix: true,
292
+ locale,
293
+ });
294
+ const absoluteLabel = formatDateTime(
295
+ savedDate,
296
+ getSettingValue,
297
+ currentLocaleCode
298
+ );
299
+
300
+ return currentLocaleCode.startsWith('pt')
301
+ ? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
302
+ : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
303
+ }, [editDraftSavedAt, currentLocaleCode, getSettingValue, hasEditDraft]);
304
+
305
+ const openCreateDialog = () => {
306
+ const storedDraft = loadCreateDraft();
307
+
308
+ form.reset(
309
+ storedDraft?.payload.mode === 'create'
310
+ ? storedDraft.payload.values
311
+ : {
312
+ code: '',
313
+ name: '',
314
+ country_code: '',
315
+ is_unique: false,
316
+ }
317
+ );
318
+ setIsDialogOpen(true);
319
+ };
320
+
153
321
  const onSubmit = async (values: z.infer<typeof documentTypeSchema>) => {
154
322
  try {
155
323
  const payload = {
@@ -169,12 +337,13 @@ export default function DocumentTypePage() {
169
337
  data: payload,
170
338
  });
171
339
 
340
+ clearCreateDraft();
172
341
  toast.success(t('successCreate'));
173
342
  setIsDialogOpen(false);
174
343
  form.reset();
175
344
  refetch();
176
- } catch (error: any) {
177
- toast.error(error?.message || t('errorCreate'));
345
+ } catch (error: unknown) {
346
+ toast.error(getErrorMessage(error, t('errorCreate')));
178
347
  }
179
348
  };
180
349
 
@@ -199,13 +368,14 @@ export default function DocumentTypePage() {
199
368
  data: payload,
200
369
  });
201
370
 
371
+ clearEditDraft();
202
372
  toast.success(t('successUpdate'));
203
373
  setIsEditDialogOpen(false);
204
374
  setEditingDocumentType(null);
205
375
  editForm.reset();
206
376
  refetch();
207
- } catch (error: any) {
208
- toast.error(error?.message || t('errorUpdate'));
377
+ } catch (error: unknown) {
378
+ toast.error(getErrorMessage(error, t('errorUpdate')));
209
379
  }
210
380
  };
211
381
 
@@ -221,8 +391,8 @@ export default function DocumentTypePage() {
221
391
  setDeleteDialogOpen(false);
222
392
  setDeletingId(null);
223
393
  refetch();
224
- } catch (error: any) {
225
- toast.error(error?.message || t('errorDelete'));
394
+ } catch (error: unknown) {
395
+ toast.error(getErrorMessage(error, t('errorDelete')));
226
396
  }
227
397
  };
228
398
 
@@ -230,26 +400,47 @@ export default function DocumentTypePage() {
230
400
  (async () => {
231
401
  setEditingDocumentType(documentType);
232
402
  try {
233
- const { data } = await request<any>({
403
+ const { data } = await request<DocumentTypeResponse>({
234
404
  url: `/person-document-type/${documentType.document_type_id || documentType.id}?locale=${currentLocaleCode}`,
235
405
  method: 'GET',
236
406
  });
237
- editForm.reset({
238
- code: data.code || documentType.code,
239
- name: data.name || documentType.name || '',
240
- country_code: data.country_code || documentType.country_code || '',
241
- is_unique:
242
- typeof data.is_unique === 'boolean'
243
- ? data.is_unique
244
- : (documentType.is_unique ?? false),
245
- });
407
+ const storedDraft = loadEditDraft();
408
+ const shouldRestoreDraft =
409
+ storedDraft?.payload.mode === 'edit' &&
410
+ storedDraft.payload.documentTypeId ===
411
+ (documentType.document_type_id || documentType.id);
412
+
413
+ editForm.reset(
414
+ shouldRestoreDraft
415
+ ? storedDraft.payload.values
416
+ : {
417
+ code: data.code || documentType.code,
418
+ name: data.name || documentType.name || '',
419
+ country_code:
420
+ data.country_code || documentType.country_code || '',
421
+ is_unique:
422
+ typeof data.is_unique === 'boolean'
423
+ ? data.is_unique
424
+ : (documentType.is_unique ?? false),
425
+ }
426
+ );
246
427
  } catch {
247
- editForm.reset({
248
- code: documentType.code,
249
- name: documentType.name || '',
250
- country_code: documentType.country_code || '',
251
- is_unique: documentType.is_unique ?? false,
252
- });
428
+ const storedDraft = loadEditDraft();
429
+ const shouldRestoreDraft =
430
+ storedDraft?.payload.mode === 'edit' &&
431
+ storedDraft.payload.documentTypeId ===
432
+ (documentType.document_type_id || documentType.id);
433
+
434
+ editForm.reset(
435
+ shouldRestoreDraft
436
+ ? storedDraft.payload.values
437
+ : {
438
+ code: documentType.code,
439
+ name: documentType.name || '',
440
+ country_code: documentType.country_code || '',
441
+ is_unique: documentType.is_unique ?? false,
442
+ }
443
+ );
253
444
  }
254
445
  setIsEditDialogOpen(true);
255
446
  })();
@@ -267,7 +458,7 @@ export default function DocumentTypePage() {
267
458
  actions={[
268
459
  {
269
460
  label: t('buttonNewType'),
270
- onClick: () => setIsDialogOpen(true),
461
+ onClick: openCreateDialog,
271
462
  variant: 'default',
272
463
  icon: <Plus />,
273
464
  },
@@ -295,7 +486,7 @@ export default function DocumentTypePage() {
295
486
  <TableHead>{t('tableSlug')}</TableHead>
296
487
  <TableHead>{t('tableName')}</TableHead>
297
488
  <TableHead>{t('tableCreatedAt')}</TableHead>
298
- <TableHead className="w-[70px]"></TableHead>
489
+ <TableHead className="w-17.5"></TableHead>
299
490
  </TableRow>
300
491
  </TableHeader>
301
492
  <TableBody>
@@ -364,7 +555,7 @@ export default function DocumentTypePage() {
364
555
  title={t('noResults')}
365
556
  description={t('emptyStateDescription')}
366
557
  actionLabel={t('emptyStateAction')}
367
- onAction={() => setIsDialogOpen(true)}
558
+ onAction={openCreateDialog}
368
559
  />
369
560
  </div>
370
561
  )}
@@ -378,7 +569,15 @@ export default function DocumentTypePage() {
378
569
  pageSizeOptions={[10, 20, 30, 40, 50]}
379
570
  />
380
571
 
381
- <Sheet open={isDialogOpen} onOpenChange={setIsDialogOpen}>
572
+ <Sheet
573
+ open={isDialogOpen}
574
+ onOpenChange={(open) => {
575
+ setIsDialogOpen(open);
576
+ if (!open) {
577
+ form.reset();
578
+ }
579
+ }}
580
+ >
382
581
  <SheetContent className="w-full sm:max-w-md">
383
582
  <SheetHeader>
384
583
  <SheetTitle>{t('dialogNewTitle')}</SheetTitle>
@@ -476,6 +675,12 @@ export default function DocumentTypePage() {
476
675
  />
477
676
  </div>
478
677
 
678
+ {createDraftStatusContent ? (
679
+ <p className="text-xs text-muted-foreground">
680
+ {createDraftStatusContent}
681
+ </p>
682
+ ) : null}
683
+
479
684
  <SheetFooter className="px-0">
480
685
  <Button type="submit">{t('buttonCreate')}</Button>
481
686
  </SheetFooter>
@@ -591,6 +796,12 @@ export default function DocumentTypePage() {
591
796
  />
592
797
  </div>
593
798
 
799
+ {editDraftStatusContent ? (
800
+ <p className="text-xs text-muted-foreground">
801
+ {editDraftStatusContent}
802
+ </p>
803
+ ) : null}
804
+
594
805
  <SheetFooter className="px-0">
595
806
  <Button type="submit">{t('buttonUpdate')}</Button>
596
807
  </SheetFooter>
@@ -53,10 +53,13 @@ import {
53
53
  } from '@/components/ui/table';
54
54
  import { Textarea } from '@/components/ui/textarea';
55
55
  import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
56
+ import { useFormDraft } from '@/hooks/use-form-draft';
56
57
  import { formatDateTime } from '@/lib/format-date';
57
58
  import { cn } from '@/lib/utils';
58
59
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
59
60
  import { zodResolver } from '@hookform/resolvers/zod';
61
+ import { formatDistanceToNow } from 'date-fns';
62
+ import { enUS, ptBR } from 'date-fns/locale';
60
63
  import {
61
64
  CalendarClock,
62
65
  CalendarDays,
@@ -74,7 +77,7 @@ import {
74
77
  } from 'lucide-react';
75
78
  import { useTranslations } from 'next-intl';
76
79
  import { useEffect, useMemo, useState } from 'react';
77
- import { useForm } from 'react-hook-form';
80
+ import { useForm, useWatch } from 'react-hook-form';
78
81
  import { toast } from 'sonner';
79
82
  import { z } from 'zod';
80
83
 
@@ -123,7 +126,18 @@ type FollowupStats = {
123
126
 
124
127
  type FollowupViewMode = 'table' | 'cards';
125
128
 
129
+ type ScheduleDraftPayload = {
130
+ mode: 'create' | 'reschedule';
131
+ personLabel: string;
132
+ values: {
133
+ personId: string;
134
+ next_action_at: string;
135
+ notes: string;
136
+ };
137
+ };
138
+
126
139
  const FOLLOWUPS_VIEW_STORAGE_KEY = 'contact-followups-view-mode';
140
+ const FOLLOWUPS_FORM_DRAFT_STORAGE_KEY = 'contact-followups-form-draft';
127
141
 
128
142
  function toInputDateTimeValue(value?: string | null) {
129
143
  if (!value) {
@@ -197,6 +211,67 @@ export default function CrmFollowupsPage() {
197
211
  const [sheetOpen, setSheetOpen] = useState(false);
198
212
  const [personPickerOpen, setPersonPickerOpen] = useState(false);
199
213
  const [isSubmitting, setIsSubmitting] = useState(false);
214
+ const [sheetMode, setSheetMode] = useState<'create' | 'reschedule'>('create');
215
+
216
+ const watchedScheduleValues = useWatch({
217
+ control: form.control,
218
+ });
219
+
220
+ const hasDraftContent = useMemo(
221
+ () =>
222
+ Boolean(
223
+ (watchedScheduleValues.personId ?? '').trim() ||
224
+ (watchedScheduleValues.next_action_at ?? '').trim() ||
225
+ (watchedScheduleValues.notes ?? '').trim()
226
+ ),
227
+ [watchedScheduleValues]
228
+ );
229
+
230
+ const {
231
+ clearDraft,
232
+ loadDraft,
233
+ hasDraft,
234
+ savedAt: draftSavedAt,
235
+ } = useFormDraft<ScheduleDraftPayload>({
236
+ storageKey: FOLLOWUPS_FORM_DRAFT_STORAGE_KEY,
237
+ value: {
238
+ mode: sheetMode,
239
+ personLabel: selectedPersonLabel,
240
+ values: {
241
+ personId: watchedScheduleValues.personId ?? '',
242
+ next_action_at: watchedScheduleValues.next_action_at ?? '',
243
+ notes: watchedScheduleValues.notes ?? '',
244
+ },
245
+ },
246
+ hasData: hasDraftContent,
247
+ enabled: sheetOpen,
248
+ });
249
+
250
+ const draftStatusContent = useMemo(() => {
251
+ if (!hasDraft || !draftSavedAt) {
252
+ return null;
253
+ }
254
+
255
+ const savedDate = new Date(draftSavedAt);
256
+ if (Number.isNaN(savedDate.getTime())) {
257
+ return null;
258
+ }
259
+
260
+ const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
261
+ const relativeLabel = formatDistanceToNow(savedDate, {
262
+ addSuffix: true,
263
+ locale,
264
+ });
265
+ const absoluteLabel = formatDateTime(
266
+ savedDate,
267
+ getSettingValue,
268
+ currentLocaleCode
269
+ );
270
+
271
+ return currentLocaleCode.startsWith('pt')
272
+ ? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
273
+ : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
274
+ }, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
200
275
 
201
276
  useEffect(() => {
202
277
  const timeout = setTimeout(() => {
@@ -394,28 +469,56 @@ export default function CrmFollowupsPage() {
394
469
  ];
395
470
 
396
471
  const openCreateSheet = () => {
397
- setSelectedPersonLabel('');
398
- setPersonSearch('');
399
- setDebouncedPersonSearch('');
472
+ setSheetMode('create');
473
+ const storedDraft = loadDraft();
474
+ const shouldRestoreDraft = storedDraft?.payload.mode === 'create';
475
+
476
+ setSelectedPersonLabel(
477
+ shouldRestoreDraft ? storedDraft.payload.personLabel : ''
478
+ );
479
+ setPersonSearch(shouldRestoreDraft ? storedDraft.payload.personLabel : '');
480
+ setDebouncedPersonSearch(
481
+ shouldRestoreDraft ? storedDraft.payload.personLabel : ''
482
+ );
400
483
  setPersonPickerOpen(false);
401
- form.reset({
402
- personId: '',
403
- next_action_at: '',
404
- notes: '',
405
- });
484
+ form.reset(
485
+ shouldRestoreDraft
486
+ ? storedDraft.payload.values
487
+ : {
488
+ personId: '',
489
+ next_action_at: '',
490
+ notes: '',
491
+ }
492
+ );
406
493
  setSheetOpen(true);
407
494
  };
408
495
 
409
496
  const openRescheduleSheet = (row: FollowupListItem) => {
410
- setSelectedPersonLabel(row.person.name);
411
- setPersonSearch(row.person.name);
412
- setDebouncedPersonSearch(row.person.name);
497
+ setSheetMode('reschedule');
498
+ const storedDraft = loadDraft();
499
+ const shouldRestoreDraft =
500
+ storedDraft?.payload.mode === 'reschedule' &&
501
+ storedDraft.payload.values.personId === String(row.person.id);
502
+
503
+ setSelectedPersonLabel(
504
+ shouldRestoreDraft ? storedDraft.payload.personLabel : row.person.name
505
+ );
506
+ setPersonSearch(
507
+ shouldRestoreDraft ? storedDraft.payload.personLabel : row.person.name
508
+ );
509
+ setDebouncedPersonSearch(
510
+ shouldRestoreDraft ? storedDraft.payload.personLabel : row.person.name
511
+ );
413
512
  setPersonPickerOpen(false);
414
- form.reset({
415
- personId: String(row.person.id),
416
- next_action_at: toInputDateTimeValue(row.next_action_at),
417
- notes: '',
418
- });
513
+ form.reset(
514
+ shouldRestoreDraft
515
+ ? storedDraft.payload.values
516
+ : {
517
+ personId: String(row.person.id),
518
+ next_action_at: toInputDateTimeValue(row.next_action_at),
519
+ notes: '',
520
+ }
521
+ );
419
522
  setSheetOpen(true);
420
523
  };
421
524
 
@@ -444,6 +547,7 @@ export default function CrmFollowupsPage() {
444
547
  },
445
548
  });
446
549
 
550
+ clearDraft();
447
551
  toast.success(t('toasts.scheduleSuccess'));
448
552
  setSheetOpen(false);
449
553
  setPersonPickerOpen(false);
@@ -457,7 +561,8 @@ export default function CrmFollowupsPage() {
457
561
 
458
562
  const selectedPersonName =
459
563
  personOptions.find(
460
- (option) => String(option.id) === String(form.watch('personId') || '')
564
+ (option) =>
565
+ String(option.id) === String(watchedScheduleValues.personId || '')
461
566
  )?.name ||
462
567
  selectedPersonLabel ||
463
568
  '';
@@ -912,7 +1017,12 @@ export default function CrmFollowupsPage() {
912
1017
  )}
913
1018
  />
914
1019
 
915
- <SheetFooter className="mt-auto border-t pt-4">
1020
+ <SheetFooter className="mt-auto flex-col items-stretch border-t pt-4">
1021
+ {draftStatusContent ? (
1022
+ <p className="text-xs text-muted-foreground">
1023
+ {draftStatusContent}
1024
+ </p>
1025
+ ) : null}
916
1026
  <Button
917
1027
  type="submit"
918
1028
  className="w-full"