@hed-hog/finance 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/hedhog/frontend/app/_components/finance-entity-field-with-create.tsx.ejs +130 -465
- package/hedhog/frontend/app/_components/finance-layout.tsx.ejs +135 -131
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +270 -14
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +175 -4
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +271 -19
- package/hedhog/frontend/app/administration/categories/page.tsx.ejs +97 -11
- package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +88 -8
- package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +94 -8
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +163 -42
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +135 -57
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +202 -49
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +107 -22
- package/hedhog/frontend/messages/en.json +174 -221
- package/hedhog/frontend/messages/pt.json +174 -221
- package/package.json +7 -7
- package/hedhog/frontend/app/_components/person-field-with-create.tsx.ejs +0 -734
|
@@ -37,12 +37,16 @@ import {
|
|
|
37
37
|
TableRow,
|
|
38
38
|
} from '@/components/ui/table';
|
|
39
39
|
import { Textarea } from '@/components/ui/textarea';
|
|
40
|
+
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
41
|
+
import { formatDateTime } from '@/lib/format-date';
|
|
40
42
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
41
43
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
44
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
45
|
+
import { enUS, ptBR } from 'date-fns/locale';
|
|
42
46
|
import { AlertTriangle, FileText, Send } from 'lucide-react';
|
|
43
47
|
import { useTranslations } from 'next-intl';
|
|
44
|
-
import { useState } from 'react';
|
|
45
|
-
import { useForm } from 'react-hook-form';
|
|
48
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
49
|
+
import { useForm, useWatch } from 'react-hook-form';
|
|
46
50
|
import {
|
|
47
51
|
Bar,
|
|
48
52
|
BarChart,
|
|
@@ -94,6 +98,21 @@ const registerAgreementSchema = z.object({
|
|
|
94
98
|
notes: z.string().optional(),
|
|
95
99
|
});
|
|
96
100
|
|
|
101
|
+
type SendCollectionDraftPayload = {
|
|
102
|
+
clienteId: string;
|
|
103
|
+
values: z.infer<typeof sendCollectionSchema>;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type RegisterAgreementDraftPayload = {
|
|
107
|
+
clienteId: string;
|
|
108
|
+
values: z.infer<typeof registerAgreementSchema>;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const SEND_COLLECTION_DRAFT_STORAGE_KEY =
|
|
112
|
+
'finance-collections-default-send-draft';
|
|
113
|
+
const REGISTER_AGREEMENT_DRAFT_STORAGE_KEY =
|
|
114
|
+
'finance-collections-default-agreement-draft';
|
|
115
|
+
|
|
97
116
|
function HistoricoContatosSheet({
|
|
98
117
|
cliente,
|
|
99
118
|
historicoContatos,
|
|
@@ -159,7 +178,8 @@ function EnviarCobrancaDialog({
|
|
|
159
178
|
onSuccess: () => Promise<unknown>;
|
|
160
179
|
t: ReturnType<typeof useTranslations>;
|
|
161
180
|
}) {
|
|
162
|
-
const { request, showToastHandler } =
|
|
181
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
182
|
+
useApp();
|
|
163
183
|
const [open, setOpen] = useState(false);
|
|
164
184
|
|
|
165
185
|
const form = useForm<z.infer<typeof sendCollectionSchema>>({
|
|
@@ -169,6 +189,70 @@ function EnviarCobrancaDialog({
|
|
|
169
189
|
},
|
|
170
190
|
});
|
|
171
191
|
|
|
192
|
+
const watchedValues = useWatch({
|
|
193
|
+
control: form.control,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const {
|
|
197
|
+
clearDraft,
|
|
198
|
+
loadDraft,
|
|
199
|
+
hasDraft,
|
|
200
|
+
savedAt: draftSavedAt,
|
|
201
|
+
} = useFormDraft<SendCollectionDraftPayload>({
|
|
202
|
+
storageKey: SEND_COLLECTION_DRAFT_STORAGE_KEY,
|
|
203
|
+
value: {
|
|
204
|
+
clienteId,
|
|
205
|
+
values: {
|
|
206
|
+
message: watchedValues.message ?? t('send.defaultMessage'),
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
hasData:
|
|
210
|
+
(watchedValues.message ?? '').trim() !== t('send.defaultMessage').trim(),
|
|
211
|
+
enabled: open,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const draftStatusContent = useMemo(() => {
|
|
215
|
+
if (!hasDraft || !draftSavedAt) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const savedDate = new Date(draftSavedAt);
|
|
220
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
225
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
226
|
+
addSuffix: true,
|
|
227
|
+
locale,
|
|
228
|
+
});
|
|
229
|
+
const absoluteLabel = formatDateTime(
|
|
230
|
+
savedDate,
|
|
231
|
+
getSettingValue,
|
|
232
|
+
currentLocaleCode
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
return currentLocaleCode.startsWith('pt')
|
|
236
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
237
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
238
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
239
|
+
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
if (!open) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const storedDraft = loadDraft();
|
|
246
|
+
|
|
247
|
+
form.reset(
|
|
248
|
+
storedDraft?.payload.clienteId === clienteId
|
|
249
|
+
? storedDraft.payload.values
|
|
250
|
+
: {
|
|
251
|
+
message: t('send.defaultMessage'),
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
}, [clienteId, form, loadDraft, open, t]);
|
|
255
|
+
|
|
172
256
|
const onSubmit = async (values: z.infer<typeof sendCollectionSchema>) => {
|
|
173
257
|
try {
|
|
174
258
|
await request({
|
|
@@ -179,6 +263,7 @@ function EnviarCobrancaDialog({
|
|
|
179
263
|
},
|
|
180
264
|
});
|
|
181
265
|
|
|
266
|
+
clearDraft();
|
|
182
267
|
await onSuccess();
|
|
183
268
|
showToastHandler?.('success', t('send.submit'));
|
|
184
269
|
setOpen(false);
|
|
@@ -240,6 +325,11 @@ function EnviarCobrancaDialog({
|
|
|
240
325
|
</FinanceSheetBody>
|
|
241
326
|
|
|
242
327
|
<div className="border-t border-border/50 px-4 py-4 sm:px-6">
|
|
328
|
+
{draftStatusContent ? (
|
|
329
|
+
<p className="mb-2 text-xs text-muted-foreground">
|
|
330
|
+
{draftStatusContent}
|
|
331
|
+
</p>
|
|
332
|
+
) : null}
|
|
243
333
|
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
244
334
|
<Button
|
|
245
335
|
type="button"
|
|
@@ -271,7 +361,8 @@ function RegistrarAcordoDialog({
|
|
|
271
361
|
onSuccess: () => Promise<unknown>;
|
|
272
362
|
t: ReturnType<typeof useTranslations>;
|
|
273
363
|
}) {
|
|
274
|
-
const { request, showToastHandler } =
|
|
364
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
365
|
+
useApp();
|
|
275
366
|
const [open, setOpen] = useState(false);
|
|
276
367
|
|
|
277
368
|
const form = useForm<z.infer<typeof registerAgreementSchema>>({
|
|
@@ -284,6 +375,80 @@ function RegistrarAcordoDialog({
|
|
|
284
375
|
},
|
|
285
376
|
});
|
|
286
377
|
|
|
378
|
+
const watchedValues = useWatch({
|
|
379
|
+
control: form.control,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const {
|
|
383
|
+
clearDraft,
|
|
384
|
+
loadDraft,
|
|
385
|
+
hasDraft,
|
|
386
|
+
savedAt: draftSavedAt,
|
|
387
|
+
} = useFormDraft<RegisterAgreementDraftPayload>({
|
|
388
|
+
storageKey: REGISTER_AGREEMENT_DRAFT_STORAGE_KEY,
|
|
389
|
+
value: {
|
|
390
|
+
clienteId,
|
|
391
|
+
values: {
|
|
392
|
+
amount: watchedValues.amount ?? 0,
|
|
393
|
+
installments: watchedValues.installments ?? 1,
|
|
394
|
+
first_due_date: watchedValues.first_due_date ?? '',
|
|
395
|
+
notes: watchedValues.notes ?? '',
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
hasData: Boolean(
|
|
399
|
+
(watchedValues.amount ?? 0) > 0 ||
|
|
400
|
+
(watchedValues.installments ?? 1) !== 1 ||
|
|
401
|
+
(watchedValues.first_due_date ?? '').trim() ||
|
|
402
|
+
(watchedValues.notes ?? '').trim()
|
|
403
|
+
),
|
|
404
|
+
enabled: open,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const draftStatusContent = useMemo(() => {
|
|
408
|
+
if (!hasDraft || !draftSavedAt) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const savedDate = new Date(draftSavedAt);
|
|
413
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
418
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
419
|
+
addSuffix: true,
|
|
420
|
+
locale,
|
|
421
|
+
});
|
|
422
|
+
const absoluteLabel = formatDateTime(
|
|
423
|
+
savedDate,
|
|
424
|
+
getSettingValue,
|
|
425
|
+
currentLocaleCode
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
return currentLocaleCode.startsWith('pt')
|
|
429
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
430
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
431
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
432
|
+
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
if (!open) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const storedDraft = loadDraft();
|
|
439
|
+
|
|
440
|
+
form.reset(
|
|
441
|
+
storedDraft?.payload.clienteId === clienteId
|
|
442
|
+
? storedDraft.payload.values
|
|
443
|
+
: {
|
|
444
|
+
amount: 0,
|
|
445
|
+
installments: 1,
|
|
446
|
+
first_due_date: '',
|
|
447
|
+
notes: '',
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
}, [clienteId, form, loadDraft, open]);
|
|
451
|
+
|
|
287
452
|
const onSubmit = async (values: z.infer<typeof registerAgreementSchema>) => {
|
|
288
453
|
try {
|
|
289
454
|
await request({
|
|
@@ -292,6 +457,7 @@ function RegistrarAcordoDialog({
|
|
|
292
457
|
data: values,
|
|
293
458
|
});
|
|
294
459
|
|
|
460
|
+
clearDraft();
|
|
295
461
|
await onSuccess();
|
|
296
462
|
showToastHandler?.('success', t('agreement.submit'));
|
|
297
463
|
setOpen(false);
|
|
@@ -410,6 +576,11 @@ function RegistrarAcordoDialog({
|
|
|
410
576
|
</FinanceSheetBody>
|
|
411
577
|
|
|
412
578
|
<div className="border-t border-border/50 px-4 py-4 sm:px-6">
|
|
579
|
+
{draftStatusContent ? (
|
|
580
|
+
<p className="mb-2 text-xs text-muted-foreground">
|
|
581
|
+
{draftStatusContent}
|
|
582
|
+
</p>
|
|
583
|
+
) : null}
|
|
413
584
|
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
414
585
|
<Button
|
|
415
586
|
type="button"
|
|
@@ -71,8 +71,12 @@ import {
|
|
|
71
71
|
TooltipContent,
|
|
72
72
|
TooltipTrigger,
|
|
73
73
|
} from '@/components/ui/tooltip';
|
|
74
|
+
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
75
|
+
import { formatDateTime } from '@/lib/format-date';
|
|
74
76
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
75
77
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
78
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
79
|
+
import { enUS, ptBR } from 'date-fns/locale';
|
|
76
80
|
import {
|
|
77
81
|
AlertTriangle,
|
|
78
82
|
Download,
|
|
@@ -92,7 +96,7 @@ import { useTranslations } from 'next-intl';
|
|
|
92
96
|
import Link from 'next/link';
|
|
93
97
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
94
98
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
95
|
-
import { useFieldArray, useForm } from 'react-hook-form';
|
|
99
|
+
import { useFieldArray, useForm, useWatch } from 'react-hook-form';
|
|
96
100
|
import { z } from 'zod';
|
|
97
101
|
import { formatarData } from '../../_lib/formatters';
|
|
98
102
|
import { useFinanceData } from '../../_lib/use-finance-data';
|
|
@@ -250,6 +254,21 @@ const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
|
|
|
250
254
|
|
|
251
255
|
type NewTitleFormValues = z.infer<ReturnType<typeof getNewTitleFormSchema>>;
|
|
252
256
|
|
|
257
|
+
type ReceivableInstallmentDraftPayload = {
|
|
258
|
+
mode: 'create' | 'edit';
|
|
259
|
+
titleId: string | null;
|
|
260
|
+
values: NewTitleFormValues;
|
|
261
|
+
uploadedFileId: number | null;
|
|
262
|
+
uploadedFileName: string;
|
|
263
|
+
isInstallmentsEdited: boolean;
|
|
264
|
+
autoRedistributeInstallments: boolean;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const RECEIVABLE_NEW_TITLE_DRAFT_STORAGE_KEY =
|
|
268
|
+
'finance-accounts-receivable-installments-new-draft';
|
|
269
|
+
const RECEIVABLE_EDIT_TITLE_DRAFT_STORAGE_KEY =
|
|
270
|
+
'finance-accounts-receivable-installments-edit-draft';
|
|
271
|
+
|
|
253
272
|
function NovoTituloSheet({
|
|
254
273
|
categorias,
|
|
255
274
|
centrosCusto,
|
|
@@ -263,7 +282,8 @@ function NovoTituloSheet({
|
|
|
263
282
|
onCreated: () => Promise<any> | void;
|
|
264
283
|
onOptionsUpdated?: () => Promise<any> | void;
|
|
265
284
|
}) {
|
|
266
|
-
const { request, showToastHandler } =
|
|
285
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
286
|
+
useApp();
|
|
267
287
|
const [open, setOpen] = useState(false);
|
|
268
288
|
const [uploadedFileId, setUploadedFileId] = useState<number | null>(null);
|
|
269
289
|
const [uploadedFileName, setUploadedFileName] = useState('');
|
|
@@ -325,13 +345,141 @@ function NovoTituloSheet({
|
|
|
325
345
|
name: 'installments',
|
|
326
346
|
});
|
|
327
347
|
|
|
348
|
+
const watchedFormValues = useWatch({
|
|
349
|
+
control: form.control,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const {
|
|
353
|
+
clearDraft,
|
|
354
|
+
loadDraft,
|
|
355
|
+
hasDraft,
|
|
356
|
+
savedAt: draftSavedAt,
|
|
357
|
+
} = useFormDraft<ReceivableInstallmentDraftPayload>({
|
|
358
|
+
storageKey: RECEIVABLE_NEW_TITLE_DRAFT_STORAGE_KEY,
|
|
359
|
+
value: {
|
|
360
|
+
mode: 'create',
|
|
361
|
+
titleId: null,
|
|
362
|
+
values: {
|
|
363
|
+
documento: watchedFormValues.documento ?? '',
|
|
364
|
+
clienteId: watchedFormValues.clienteId ?? '',
|
|
365
|
+
competencia: watchedFormValues.competencia ?? '',
|
|
366
|
+
vencimento: watchedFormValues.vencimento ?? '',
|
|
367
|
+
valor: watchedFormValues.valor ?? 0,
|
|
368
|
+
installmentsCount: watchedFormValues.installmentsCount ?? 1,
|
|
369
|
+
installments: watchedFormValues.installments?.map((installment) => ({
|
|
370
|
+
dueDate: installment?.dueDate ?? '',
|
|
371
|
+
amount: Number(installment?.amount ?? 0),
|
|
372
|
+
})) ?? [{ dueDate: '', amount: 0 }],
|
|
373
|
+
categoriaId: watchedFormValues.categoriaId ?? '',
|
|
374
|
+
centroCustoId: watchedFormValues.centroCustoId ?? '',
|
|
375
|
+
canal: watchedFormValues.canal ?? '',
|
|
376
|
+
descricao: watchedFormValues.descricao ?? '',
|
|
377
|
+
},
|
|
378
|
+
uploadedFileId,
|
|
379
|
+
uploadedFileName,
|
|
380
|
+
isInstallmentsEdited,
|
|
381
|
+
autoRedistributeInstallments,
|
|
382
|
+
},
|
|
383
|
+
hasData: Boolean(
|
|
384
|
+
(watchedFormValues.documento ?? '').trim() ||
|
|
385
|
+
(watchedFormValues.clienteId ?? '').trim() ||
|
|
386
|
+
(watchedFormValues.competencia ?? '').trim() ||
|
|
387
|
+
(watchedFormValues.vencimento ?? '').trim() ||
|
|
388
|
+
(watchedFormValues.categoriaId ?? '').trim() ||
|
|
389
|
+
(watchedFormValues.centroCustoId ?? '').trim() ||
|
|
390
|
+
(watchedFormValues.canal ?? '').trim() ||
|
|
391
|
+
(watchedFormValues.descricao ?? '').trim() ||
|
|
392
|
+
(watchedFormValues.valor ?? 0) > 0 ||
|
|
393
|
+
(watchedFormValues.installmentsCount ?? 1) !== 1 ||
|
|
394
|
+
(watchedFormValues.installments ?? []).some(
|
|
395
|
+
(installment) =>
|
|
396
|
+
(installment?.dueDate ?? '').trim() ||
|
|
397
|
+
Number(installment?.amount ?? 0) > 0
|
|
398
|
+
) ||
|
|
399
|
+
uploadedFileId != null ||
|
|
400
|
+
uploadedFileName.trim()
|
|
401
|
+
),
|
|
402
|
+
enabled: open,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const draftStatusContent = useMemo(() => {
|
|
406
|
+
if (!hasDraft || !draftSavedAt) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const savedDate = new Date(draftSavedAt);
|
|
411
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
416
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
417
|
+
addSuffix: true,
|
|
418
|
+
locale,
|
|
419
|
+
});
|
|
420
|
+
const absoluteLabel = formatDateTime(
|
|
421
|
+
savedDate,
|
|
422
|
+
getSettingValue,
|
|
423
|
+
currentLocaleCode
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
return currentLocaleCode.startsWith('pt')
|
|
427
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
428
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
429
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
430
|
+
|
|
328
431
|
const watchedInstallmentsCount = form.watch('installmentsCount');
|
|
329
432
|
const watchedTotalValue = form.watch('valor');
|
|
330
433
|
const watchedDueDate = form.watch('vencimento');
|
|
331
434
|
const watchedInstallments = form.watch('installments');
|
|
332
435
|
|
|
333
436
|
useEffect(() => {
|
|
334
|
-
if (
|
|
437
|
+
if (!open) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const storedDraft = loadDraft();
|
|
442
|
+
|
|
443
|
+
if (storedDraft?.payload.mode === 'create') {
|
|
444
|
+
form.reset(storedDraft.payload.values);
|
|
445
|
+
setUploadedFileId(storedDraft.payload.uploadedFileId ?? null);
|
|
446
|
+
setUploadedFileName(storedDraft.payload.uploadedFileName ?? '');
|
|
447
|
+
setExtractionConfidence(null);
|
|
448
|
+
setExtractionWarnings([]);
|
|
449
|
+
setUploadProgress(storedDraft.payload.uploadedFileId ? 100 : 0);
|
|
450
|
+
setIsInstallmentsEdited(
|
|
451
|
+
storedDraft.payload.isInstallmentsEdited ?? false
|
|
452
|
+
);
|
|
453
|
+
setAutoRedistributeInstallments(
|
|
454
|
+
storedDraft.payload.autoRedistributeInstallments ?? true
|
|
455
|
+
);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
form.reset({
|
|
460
|
+
documento: '',
|
|
461
|
+
clienteId: '',
|
|
462
|
+
competencia: '',
|
|
463
|
+
vencimento: '',
|
|
464
|
+
valor: 0,
|
|
465
|
+
installmentsCount: 1,
|
|
466
|
+
installments: [{ dueDate: '', amount: 0 }],
|
|
467
|
+
categoriaId: '',
|
|
468
|
+
centroCustoId: '',
|
|
469
|
+
canal: '',
|
|
470
|
+
descricao: '',
|
|
471
|
+
});
|
|
472
|
+
setUploadedFileId(null);
|
|
473
|
+
setUploadedFileName('');
|
|
474
|
+
setExtractionConfidence(null);
|
|
475
|
+
setExtractionWarnings([]);
|
|
476
|
+
setUploadProgress(0);
|
|
477
|
+
setIsInstallmentsEdited(false);
|
|
478
|
+
setAutoRedistributeInstallments(true);
|
|
479
|
+
}, [form, loadDraft, open]);
|
|
480
|
+
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
if (isInstallmentsEdited || !open) {
|
|
335
483
|
return;
|
|
336
484
|
}
|
|
337
485
|
|
|
@@ -344,6 +492,7 @@ function NovoTituloSheet({
|
|
|
344
492
|
);
|
|
345
493
|
}, [
|
|
346
494
|
isInstallmentsEdited,
|
|
495
|
+
open,
|
|
347
496
|
replaceInstallments,
|
|
348
497
|
watchedDueDate,
|
|
349
498
|
watchedInstallmentsCount,
|
|
@@ -444,6 +593,7 @@ function NovoTituloSheet({
|
|
|
444
593
|
},
|
|
445
594
|
});
|
|
446
595
|
|
|
596
|
+
clearDraft();
|
|
447
597
|
await onCreated();
|
|
448
598
|
form.reset();
|
|
449
599
|
setUploadedFileId(null);
|
|
@@ -460,14 +610,6 @@ function NovoTituloSheet({
|
|
|
460
610
|
};
|
|
461
611
|
|
|
462
612
|
const handleCancel = () => {
|
|
463
|
-
form.reset();
|
|
464
|
-
setUploadedFileId(null);
|
|
465
|
-
setUploadedFileName('');
|
|
466
|
-
setExtractionConfidence(null);
|
|
467
|
-
setExtractionWarnings([]);
|
|
468
|
-
setUploadProgress(0);
|
|
469
|
-
setIsInstallmentsEdited(false);
|
|
470
|
-
setAutoRedistributeInstallments(true);
|
|
471
613
|
setOpen(false);
|
|
472
614
|
};
|
|
473
615
|
|
|
@@ -1130,12 +1272,13 @@ function NovoTituloSheet({
|
|
|
1130
1272
|
</FinanceSheetBody>
|
|
1131
1273
|
|
|
1132
1274
|
<div className="border-t border-border/50 px-4 py-4 sm:px-6">
|
|
1275
|
+
{draftStatusContent ? (
|
|
1276
|
+
<p className="mb-2 text-xs text-muted-foreground">
|
|
1277
|
+
{draftStatusContent}
|
|
1278
|
+
</p>
|
|
1279
|
+
) : null}
|
|
1133
1280
|
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
1134
|
-
<Button
|
|
1135
|
-
type="button"
|
|
1136
|
-
variant="outline"
|
|
1137
|
-
onClick={() => setOpen(false)}
|
|
1138
|
-
>
|
|
1281
|
+
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
1139
1282
|
{t('common.cancel')}
|
|
1140
1283
|
</Button>
|
|
1141
1284
|
<Button
|
|
@@ -1183,7 +1326,8 @@ function EditarTituloSheet({
|
|
|
1183
1326
|
onUpdated: () => Promise<any> | void;
|
|
1184
1327
|
onOptionsUpdated?: () => Promise<any> | void;
|
|
1185
1328
|
}) {
|
|
1186
|
-
const { request, showToastHandler } =
|
|
1329
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
1330
|
+
useApp();
|
|
1187
1331
|
const [uploadedFileId, setUploadedFileId] = useState<number | null>(null);
|
|
1188
1332
|
const [uploadedFileName, setUploadedFileName] = useState('');
|
|
1189
1333
|
const [isUploadingFile, setIsUploadingFile] = useState(false);
|
|
@@ -1244,6 +1388,89 @@ function EditarTituloSheet({
|
|
|
1244
1388
|
name: 'installments',
|
|
1245
1389
|
});
|
|
1246
1390
|
|
|
1391
|
+
const watchedFormValues = useWatch({
|
|
1392
|
+
control: form.control,
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
const {
|
|
1396
|
+
clearDraft,
|
|
1397
|
+
loadDraft,
|
|
1398
|
+
hasDraft,
|
|
1399
|
+
savedAt: draftSavedAt,
|
|
1400
|
+
} = useFormDraft<ReceivableInstallmentDraftPayload>({
|
|
1401
|
+
storageKey: RECEIVABLE_EDIT_TITLE_DRAFT_STORAGE_KEY,
|
|
1402
|
+
value: {
|
|
1403
|
+
mode: 'edit',
|
|
1404
|
+
titleId: titulo?.id ? String(titulo.id) : null,
|
|
1405
|
+
values: {
|
|
1406
|
+
documento: watchedFormValues.documento ?? '',
|
|
1407
|
+
clienteId: watchedFormValues.clienteId ?? '',
|
|
1408
|
+
competencia: watchedFormValues.competencia ?? '',
|
|
1409
|
+
vencimento: watchedFormValues.vencimento ?? '',
|
|
1410
|
+
valor: watchedFormValues.valor ?? 0,
|
|
1411
|
+
installmentsCount: watchedFormValues.installmentsCount ?? 1,
|
|
1412
|
+
installments: watchedFormValues.installments?.map((installment) => ({
|
|
1413
|
+
dueDate: installment?.dueDate ?? '',
|
|
1414
|
+
amount: Number(installment?.amount ?? 0),
|
|
1415
|
+
})) ?? [{ dueDate: '', amount: 0 }],
|
|
1416
|
+
categoriaId: watchedFormValues.categoriaId ?? '',
|
|
1417
|
+
centroCustoId: watchedFormValues.centroCustoId ?? '',
|
|
1418
|
+
canal: watchedFormValues.canal ?? '',
|
|
1419
|
+
descricao: watchedFormValues.descricao ?? '',
|
|
1420
|
+
},
|
|
1421
|
+
uploadedFileId,
|
|
1422
|
+
uploadedFileName,
|
|
1423
|
+
isInstallmentsEdited,
|
|
1424
|
+
autoRedistributeInstallments,
|
|
1425
|
+
},
|
|
1426
|
+
hasData: Boolean(
|
|
1427
|
+
titulo &&
|
|
1428
|
+
((watchedFormValues.documento ?? '').trim() ||
|
|
1429
|
+
(watchedFormValues.clienteId ?? '').trim() ||
|
|
1430
|
+
(watchedFormValues.competencia ?? '').trim() ||
|
|
1431
|
+
(watchedFormValues.vencimento ?? '').trim() ||
|
|
1432
|
+
(watchedFormValues.categoriaId ?? '').trim() ||
|
|
1433
|
+
(watchedFormValues.centroCustoId ?? '').trim() ||
|
|
1434
|
+
(watchedFormValues.canal ?? '').trim() ||
|
|
1435
|
+
(watchedFormValues.descricao ?? '').trim() ||
|
|
1436
|
+
(watchedFormValues.valor ?? 0) > 0 ||
|
|
1437
|
+
(watchedFormValues.installments ?? []).some(
|
|
1438
|
+
(installment) =>
|
|
1439
|
+
(installment?.dueDate ?? '').trim() ||
|
|
1440
|
+
Number(installment?.amount ?? 0) > 0
|
|
1441
|
+
) ||
|
|
1442
|
+
uploadedFileId != null ||
|
|
1443
|
+
uploadedFileName.trim())
|
|
1444
|
+
),
|
|
1445
|
+
enabled: open,
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
const draftStatusContent = useMemo(() => {
|
|
1449
|
+
if (!hasDraft || !draftSavedAt) {
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const savedDate = new Date(draftSavedAt);
|
|
1454
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
1459
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
1460
|
+
addSuffix: true,
|
|
1461
|
+
locale,
|
|
1462
|
+
});
|
|
1463
|
+
const absoluteLabel = formatDateTime(
|
|
1464
|
+
savedDate,
|
|
1465
|
+
getSettingValue,
|
|
1466
|
+
currentLocaleCode
|
|
1467
|
+
);
|
|
1468
|
+
|
|
1469
|
+
return currentLocaleCode.startsWith('pt')
|
|
1470
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
1471
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
1472
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
1473
|
+
|
|
1247
1474
|
const watchedInstallmentsCount = form.watch('installmentsCount');
|
|
1248
1475
|
const watchedTotalValue = form.watch('valor');
|
|
1249
1476
|
const watchedDueDate = form.watch('vencimento');
|
|
@@ -1271,6 +1498,25 @@ function EditarTituloSheet({
|
|
|
1271
1498
|
return;
|
|
1272
1499
|
}
|
|
1273
1500
|
|
|
1501
|
+
const storedDraft = loadDraft();
|
|
1502
|
+
const shouldRestoreDraft =
|
|
1503
|
+
storedDraft?.payload.mode === 'edit' &&
|
|
1504
|
+
String(storedDraft.payload.titleId ?? '') === String(titulo.id ?? '');
|
|
1505
|
+
|
|
1506
|
+
if (shouldRestoreDraft) {
|
|
1507
|
+
form.reset(storedDraft.payload.values);
|
|
1508
|
+
setUploadedFileId(storedDraft.payload.uploadedFileId ?? null);
|
|
1509
|
+
setUploadedFileName(storedDraft.payload.uploadedFileName ?? '');
|
|
1510
|
+
setExtractionConfidence(null);
|
|
1511
|
+
setExtractionWarnings([]);
|
|
1512
|
+
setUploadProgress(storedDraft.payload.uploadedFileId ? 100 : 0);
|
|
1513
|
+
setIsInstallmentsEdited(storedDraft.payload.isInstallmentsEdited ?? true);
|
|
1514
|
+
setAutoRedistributeInstallments(
|
|
1515
|
+
storedDraft.payload.autoRedistributeInstallments ?? true
|
|
1516
|
+
);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1274
1520
|
const installments = Array.isArray(titulo.parcelas) ? titulo.parcelas : [];
|
|
1275
1521
|
const normalizedInstallments =
|
|
1276
1522
|
installments.length > 0
|
|
@@ -1333,9 +1579,9 @@ function EditarTituloSheet({
|
|
|
1333
1579
|
setExtractionConfidence(null);
|
|
1334
1580
|
setExtractionWarnings([]);
|
|
1335
1581
|
setUploadProgress(0);
|
|
1336
|
-
|
|
1582
|
+
setAutoRedistributeInstallments(true);
|
|
1337
1583
|
setIsInstallmentsEdited(true);
|
|
1338
|
-
}, [form, open, titulo]);
|
|
1584
|
+
}, [form, loadDraft, open, titulo]);
|
|
1339
1585
|
|
|
1340
1586
|
useEffect(() => {
|
|
1341
1587
|
if (isInstallmentsEdited || !open) {
|
|
@@ -1620,6 +1866,7 @@ function EditarTituloSheet({
|
|
|
1620
1866
|
},
|
|
1621
1867
|
});
|
|
1622
1868
|
|
|
1869
|
+
clearDraft();
|
|
1623
1870
|
await onUpdated();
|
|
1624
1871
|
showToastHandler?.('success', t('messages.updateSuccess'));
|
|
1625
1872
|
onOpenChange(false);
|
|
@@ -2127,6 +2374,11 @@ function EditarTituloSheet({
|
|
|
2127
2374
|
</FinanceSheetBody>
|
|
2128
2375
|
|
|
2129
2376
|
<div className="border-t border-border/50 px-4 py-4 sm:px-6">
|
|
2377
|
+
{draftStatusContent ? (
|
|
2378
|
+
<p className="mb-2 text-xs text-muted-foreground">
|
|
2379
|
+
{draftStatusContent}
|
|
2380
|
+
</p>
|
|
2381
|
+
) : null}
|
|
2130
2382
|
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
2131
2383
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
2132
2384
|
{t('common.cancel')}
|