@hed-hog/contact 0.0.303 → 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 +696 -82
- package/hedhog/frontend/messages/en.json +140 -2
- package/hedhog/frontend/messages/pt.json +147 -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
|
@@ -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 {
|
|
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:
|
|
177
|
-
toast.error(error
|
|
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:
|
|
208
|
-
toast.error(error
|
|
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:
|
|
225
|
-
toast.error(error
|
|
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<
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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:
|
|
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-
|
|
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={
|
|
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
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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) =>
|
|
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"
|