@hed-hog/finance 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/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
|
@@ -47,8 +47,12 @@ import {
|
|
|
47
47
|
TableHeader,
|
|
48
48
|
TableRow,
|
|
49
49
|
} from '@/components/ui/table';
|
|
50
|
+
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
51
|
+
import { formatDateTime } from '@/lib/format-date';
|
|
50
52
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
51
53
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
54
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
55
|
+
import { enUS, ptBR } from 'date-fns/locale';
|
|
52
56
|
import {
|
|
53
57
|
ArrowDownRight,
|
|
54
58
|
ArrowUpRight,
|
|
@@ -58,8 +62,8 @@ import {
|
|
|
58
62
|
} from 'lucide-react';
|
|
59
63
|
import { useTranslations } from 'next-intl';
|
|
60
64
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
61
|
-
import { useEffect, useState } from 'react';
|
|
62
|
-
import { useForm } from 'react-hook-form';
|
|
65
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
66
|
+
import { useForm, useWatch } from 'react-hook-form';
|
|
63
67
|
import { z } from 'zod';
|
|
64
68
|
import { formatarData } from '../../_lib/formatters';
|
|
65
69
|
|
|
@@ -108,6 +112,21 @@ const importStatementSchema = z.object({
|
|
|
108
112
|
type ImportStatementFormValues = z.infer<typeof importStatementSchema>;
|
|
109
113
|
type BankAccountFormValues = z.infer<typeof bankAccountFormSchema>;
|
|
110
114
|
|
|
115
|
+
type StatementBankAccountDraftPayload = {
|
|
116
|
+
values: BankAccountFormValues;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
type StatementImportDraftPayload = {
|
|
120
|
+
defaultBankAccountId?: string;
|
|
121
|
+
values: {
|
|
122
|
+
bankAccountId: string;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const STATEMENT_BANK_ACCOUNT_DRAFT_STORAGE_KEY =
|
|
127
|
+
'finance-statements-bank-account-draft';
|
|
128
|
+
const STATEMENT_IMPORT_DRAFT_STORAGE_KEY = 'finance-statements-import-draft';
|
|
129
|
+
|
|
111
130
|
function NovaContaBancariaSheet({
|
|
112
131
|
open,
|
|
113
132
|
onOpenChange,
|
|
@@ -118,7 +137,8 @@ function NovaContaBancariaSheet({
|
|
|
118
137
|
onCreated: (createdBankAccountId?: string) => Promise<void> | void;
|
|
119
138
|
}) {
|
|
120
139
|
const tBank = useTranslations('finance.BankAccountsPage');
|
|
121
|
-
const { request, showToastHandler } =
|
|
140
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
141
|
+
useApp();
|
|
122
142
|
|
|
123
143
|
const form = useForm<BankAccountFormValues>({
|
|
124
144
|
resolver: zodResolver(bankAccountFormSchema),
|
|
@@ -132,20 +152,82 @@ function NovaContaBancariaSheet({
|
|
|
132
152
|
},
|
|
133
153
|
});
|
|
134
154
|
|
|
155
|
+
const watchedBankValues = useWatch({
|
|
156
|
+
control: form.control,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const {
|
|
160
|
+
clearDraft,
|
|
161
|
+
loadDraft,
|
|
162
|
+
hasDraft,
|
|
163
|
+
savedAt: draftSavedAt,
|
|
164
|
+
} = useFormDraft<StatementBankAccountDraftPayload>({
|
|
165
|
+
storageKey: STATEMENT_BANK_ACCOUNT_DRAFT_STORAGE_KEY,
|
|
166
|
+
value: {
|
|
167
|
+
values: {
|
|
168
|
+
banco: watchedBankValues.banco ?? '',
|
|
169
|
+
agencia: watchedBankValues.agencia ?? '',
|
|
170
|
+
conta: watchedBankValues.conta ?? '',
|
|
171
|
+
tipo: watchedBankValues.tipo ?? '',
|
|
172
|
+
descricao: watchedBankValues.descricao ?? '',
|
|
173
|
+
saldoInicial: watchedBankValues.saldoInicial ?? 0,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
hasData: Boolean(
|
|
177
|
+
(watchedBankValues.banco ?? '').trim() ||
|
|
178
|
+
(watchedBankValues.agencia ?? '').trim() ||
|
|
179
|
+
(watchedBankValues.conta ?? '').trim() ||
|
|
180
|
+
(watchedBankValues.tipo ?? '').trim() ||
|
|
181
|
+
(watchedBankValues.descricao ?? '').trim() ||
|
|
182
|
+
(watchedBankValues.saldoInicial ?? 0) > 0
|
|
183
|
+
),
|
|
184
|
+
enabled: open,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const draftStatusContent = useMemo(() => {
|
|
188
|
+
if (!hasDraft || !draftSavedAt) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const savedDate = new Date(draftSavedAt);
|
|
193
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
198
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
199
|
+
addSuffix: true,
|
|
200
|
+
locale,
|
|
201
|
+
});
|
|
202
|
+
const absoluteLabel = formatDateTime(
|
|
203
|
+
savedDate,
|
|
204
|
+
getSettingValue,
|
|
205
|
+
currentLocaleCode
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return currentLocaleCode.startsWith('pt')
|
|
209
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
210
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
211
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
212
|
+
|
|
135
213
|
useEffect(() => {
|
|
136
214
|
if (!open) {
|
|
137
215
|
return;
|
|
138
216
|
}
|
|
139
217
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
218
|
+
const storedDraft = loadDraft();
|
|
219
|
+
|
|
220
|
+
form.reset(
|
|
221
|
+
storedDraft?.payload.values ?? {
|
|
222
|
+
banco: '',
|
|
223
|
+
agencia: '',
|
|
224
|
+
conta: '',
|
|
225
|
+
tipo: '',
|
|
226
|
+
descricao: '',
|
|
227
|
+
saldoInicial: 0,
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}, [form, loadDraft, open]);
|
|
149
231
|
|
|
150
232
|
const handleSubmit = async (values: BankAccountFormValues) => {
|
|
151
233
|
try {
|
|
@@ -164,6 +246,7 @@ function NovaContaBancariaSheet({
|
|
|
164
246
|
|
|
165
247
|
const createdBankAccountId = response?.data?.id;
|
|
166
248
|
|
|
249
|
+
clearDraft();
|
|
167
250
|
await onCreated(
|
|
168
251
|
createdBankAccountId !== undefined
|
|
169
252
|
? String(createdBankAccountId)
|
|
@@ -327,6 +410,12 @@ function NovaContaBancariaSheet({
|
|
|
327
410
|
)}
|
|
328
411
|
/>
|
|
329
412
|
|
|
413
|
+
{draftStatusContent ? (
|
|
414
|
+
<p className="text-xs text-muted-foreground">
|
|
415
|
+
{draftStatusContent}
|
|
416
|
+
</p>
|
|
417
|
+
) : null}
|
|
418
|
+
|
|
330
419
|
<div className="flex justify-end gap-2 pt-2">
|
|
331
420
|
<Button
|
|
332
421
|
type="button"
|
|
@@ -358,12 +447,13 @@ function ImportarExtratoSheet({
|
|
|
358
447
|
contasBancarias: BankAccount[];
|
|
359
448
|
t: ReturnType<typeof useTranslations>;
|
|
360
449
|
defaultBankAccountId?: string;
|
|
361
|
-
onImported: () => Promise<
|
|
450
|
+
onImported: () => Promise<unknown> | void;
|
|
362
451
|
onBankAccountCreated: (createdBankAccountId?: string) => Promise<void> | void;
|
|
363
452
|
open?: boolean;
|
|
364
453
|
onOpenChange?: (open: boolean) => void;
|
|
365
454
|
}) {
|
|
366
|
-
const { request, showToastHandler } =
|
|
455
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
456
|
+
useApp();
|
|
367
457
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
368
458
|
const [openNovaContaSheet, setOpenNovaContaSheet] = useState(false);
|
|
369
459
|
const isOpen = open ?? internalOpen;
|
|
@@ -381,16 +471,69 @@ function ImportarExtratoSheet({
|
|
|
381
471
|
},
|
|
382
472
|
});
|
|
383
473
|
|
|
474
|
+
const watchedImportValues = useWatch({
|
|
475
|
+
control: form.control,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const {
|
|
479
|
+
clearDraft,
|
|
480
|
+
loadDraft,
|
|
481
|
+
hasDraft,
|
|
482
|
+
savedAt: draftSavedAt,
|
|
483
|
+
} = useFormDraft<StatementImportDraftPayload>({
|
|
484
|
+
storageKey: STATEMENT_IMPORT_DRAFT_STORAGE_KEY,
|
|
485
|
+
value: {
|
|
486
|
+
defaultBankAccountId,
|
|
487
|
+
values: {
|
|
488
|
+
bankAccountId: watchedImportValues.bankAccountId ?? '',
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
hasData: Boolean(
|
|
492
|
+
(watchedImportValues.bankAccountId ?? '').trim() &&
|
|
493
|
+
(watchedImportValues.bankAccountId ?? '') !== (defaultBankAccountId ?? '')
|
|
494
|
+
),
|
|
495
|
+
enabled: isOpen,
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const draftStatusContent = useMemo(() => {
|
|
499
|
+
if (!hasDraft || !draftSavedAt) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const savedDate = new Date(draftSavedAt);
|
|
504
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
509
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
510
|
+
addSuffix: true,
|
|
511
|
+
locale,
|
|
512
|
+
});
|
|
513
|
+
const absoluteLabel = formatDateTime(
|
|
514
|
+
savedDate,
|
|
515
|
+
getSettingValue,
|
|
516
|
+
currentLocaleCode
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
return currentLocaleCode.startsWith('pt')
|
|
520
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
521
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
522
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
523
|
+
|
|
384
524
|
useEffect(() => {
|
|
385
525
|
if (!isOpen) {
|
|
386
526
|
return;
|
|
387
527
|
}
|
|
388
528
|
|
|
529
|
+
const storedDraft = loadDraft();
|
|
530
|
+
|
|
389
531
|
form.reset({
|
|
390
|
-
bankAccountId:
|
|
532
|
+
bankAccountId:
|
|
533
|
+
storedDraft?.payload.values.bankAccountId || defaultBankAccountId || '',
|
|
391
534
|
file: undefined as unknown as File,
|
|
392
535
|
});
|
|
393
|
-
}, [defaultBankAccountId, form, isOpen]);
|
|
536
|
+
}, [defaultBankAccountId, form, isOpen, loadDraft]);
|
|
394
537
|
|
|
395
538
|
const handleSubmit = async (values: ImportStatementFormValues) => {
|
|
396
539
|
const formData = new FormData();
|
|
@@ -404,6 +547,7 @@ function ImportarExtratoSheet({
|
|
|
404
547
|
data: formData,
|
|
405
548
|
});
|
|
406
549
|
|
|
550
|
+
clearDraft();
|
|
407
551
|
await onImported();
|
|
408
552
|
showToastHandler?.('success', 'Extrato importado com sucesso');
|
|
409
553
|
handleOpenChange(false);
|
|
@@ -512,6 +656,12 @@ function ImportarExtratoSheet({
|
|
|
512
656
|
)}
|
|
513
657
|
/>
|
|
514
658
|
|
|
659
|
+
{draftStatusContent ? (
|
|
660
|
+
<p className="text-xs text-muted-foreground">
|
|
661
|
+
{draftStatusContent}
|
|
662
|
+
</p>
|
|
663
|
+
) : null}
|
|
664
|
+
|
|
515
665
|
<div className="flex justify-end gap-2 pt-2">
|
|
516
666
|
<Button
|
|
517
667
|
type="button"
|
|
@@ -539,7 +689,9 @@ export default function ExtratosPage() {
|
|
|
539
689
|
const searchParams = useSearchParams();
|
|
540
690
|
const bankAccountIdFromUrl = searchParams.get('bank_account_id');
|
|
541
691
|
|
|
542
|
-
const [contaFilter, setContaFilter] = useState<string>(
|
|
692
|
+
const [contaFilter, setContaFilter] = useState<string>(
|
|
693
|
+
() => bankAccountIdFromUrl || ''
|
|
694
|
+
);
|
|
543
695
|
const [search, setSearch] = useState('');
|
|
544
696
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
545
697
|
const [isImportSheetOpen, setIsImportSheetOpen] = useState(false);
|
|
@@ -583,50 +735,51 @@ export default function ExtratosPage() {
|
|
|
583
735
|
router.replace(`${pathname}?${params.toString()}`);
|
|
584
736
|
};
|
|
585
737
|
|
|
586
|
-
|
|
738
|
+
const activeContaFilter = useMemo(() => {
|
|
587
739
|
const firstAccount = contasBancarias[0];
|
|
588
740
|
|
|
589
741
|
if (!firstAccount) {
|
|
590
|
-
return;
|
|
742
|
+
return '';
|
|
591
743
|
}
|
|
592
744
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
contasBancarias.some((account) => account.id ===
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
: firstAccount.id;
|
|
745
|
+
if (
|
|
746
|
+
contaFilter &&
|
|
747
|
+
contasBancarias.some((account) => account.id === contaFilter)
|
|
748
|
+
) {
|
|
749
|
+
return contaFilter;
|
|
750
|
+
}
|
|
600
751
|
|
|
601
|
-
if (
|
|
602
|
-
|
|
752
|
+
if (
|
|
753
|
+
bankAccountIdFromUrl &&
|
|
754
|
+
contasBancarias.some((account) => account.id === bankAccountIdFromUrl)
|
|
755
|
+
) {
|
|
756
|
+
return bankAccountIdFromUrl;
|
|
603
757
|
}
|
|
604
758
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
759
|
+
return firstAccount.id;
|
|
760
|
+
}, [bankAccountIdFromUrl, contaFilter, contasBancarias]);
|
|
761
|
+
|
|
762
|
+
useEffect(() => {
|
|
763
|
+
if (!activeContaFilter || bankAccountIdFromUrl === activeContaFilter) {
|
|
764
|
+
return;
|
|
609
765
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
router,
|
|
616
|
-
searchParams,
|
|
617
|
-
]);
|
|
766
|
+
|
|
767
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
768
|
+
params.set('bank_account_id', activeContaFilter);
|
|
769
|
+
router.replace(`${pathname}?${params.toString()}`);
|
|
770
|
+
}, [activeContaFilter, bankAccountIdFromUrl, pathname, router, searchParams]);
|
|
618
771
|
|
|
619
772
|
const { data: extratos = [], refetch: refetchExtratos } = useQuery<
|
|
620
773
|
Statement[]
|
|
621
774
|
>({
|
|
622
|
-
queryKey: ['finance-statements',
|
|
775
|
+
queryKey: ['finance-statements', activeContaFilter, debouncedSearch],
|
|
623
776
|
queryFn: async () => {
|
|
624
|
-
if (!
|
|
777
|
+
if (!activeContaFilter) {
|
|
625
778
|
return [];
|
|
626
779
|
}
|
|
627
780
|
|
|
628
781
|
const params = new URLSearchParams();
|
|
629
|
-
params.set('bank_account_id',
|
|
782
|
+
params.set('bank_account_id', activeContaFilter);
|
|
630
783
|
|
|
631
784
|
const trimmedSearch = debouncedSearch.trim();
|
|
632
785
|
if (trimmedSearch) {
|
|
@@ -642,7 +795,7 @@ export default function ExtratosPage() {
|
|
|
642
795
|
},
|
|
643
796
|
});
|
|
644
797
|
|
|
645
|
-
const conta = contasBancarias.find((item) => item.id ===
|
|
798
|
+
const conta = contasBancarias.find((item) => item.id === activeContaFilter);
|
|
646
799
|
const contaExtratoSelecionado = extratoSelecionado
|
|
647
800
|
? contasBancarias.find(
|
|
648
801
|
(item) => item.id === extratoSelecionado.contaBancariaId
|
|
@@ -685,14 +838,14 @@ export default function ExtratosPage() {
|
|
|
685
838
|
];
|
|
686
839
|
|
|
687
840
|
const handleExport = async () => {
|
|
688
|
-
if (!
|
|
841
|
+
if (!activeContaFilter) {
|
|
689
842
|
showToastHandler?.('error', 'Selecione uma conta bancária para exportar');
|
|
690
843
|
return;
|
|
691
844
|
}
|
|
692
845
|
|
|
693
846
|
try {
|
|
694
847
|
const params = new URLSearchParams();
|
|
695
|
-
params.set('bank_account_id',
|
|
848
|
+
params.set('bank_account_id', activeContaFilter);
|
|
696
849
|
|
|
697
850
|
const trimmedSearch = search.trim();
|
|
698
851
|
if (trimmedSearch) {
|
|
@@ -714,7 +867,7 @@ export default function ExtratosPage() {
|
|
|
714
867
|
: null;
|
|
715
868
|
const fileName = fileNameMatch?.[1]
|
|
716
869
|
? decodeURIComponent(fileNameMatch[1])
|
|
717
|
-
: `extrato-bancario-${
|
|
870
|
+
: `extrato-bancario-${activeContaFilter}.csv`;
|
|
718
871
|
|
|
719
872
|
const blob = new Blob([response.data], {
|
|
720
873
|
type: 'text/csv;charset=utf-8;',
|
|
@@ -752,7 +905,7 @@ export default function ExtratosPage() {
|
|
|
752
905
|
<ImportarExtratoSheet
|
|
753
906
|
contasBancarias={contasBancarias}
|
|
754
907
|
t={t}
|
|
755
|
-
defaultBankAccountId={
|
|
908
|
+
defaultBankAccountId={activeContaFilter}
|
|
756
909
|
open={isImportSheetOpen}
|
|
757
910
|
onOpenChange={setIsImportSheetOpen}
|
|
758
911
|
onImported={refetchExtratos}
|
|
@@ -764,7 +917,7 @@ export default function ExtratosPage() {
|
|
|
764
917
|
|
|
765
918
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
|
|
766
919
|
<Select
|
|
767
|
-
value={
|
|
920
|
+
value={activeContaFilter}
|
|
768
921
|
onValueChange={(value) => {
|
|
769
922
|
setContaFilter(value);
|
|
770
923
|
|
|
@@ -39,12 +39,16 @@ import {
|
|
|
39
39
|
TableRow,
|
|
40
40
|
} from '@/components/ui/table';
|
|
41
41
|
import { Textarea } from '@/components/ui/textarea';
|
|
42
|
+
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
43
|
+
import { formatDateTime } from '@/lib/format-date';
|
|
42
44
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
43
45
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
46
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
47
|
+
import { enUS, ptBR } from 'date-fns/locale';
|
|
44
48
|
import { ArrowRight, Plus } from 'lucide-react';
|
|
45
49
|
import { useTranslations } from 'next-intl';
|
|
46
|
-
import { useEffect, useState } from 'react';
|
|
47
|
-
import { useForm } from 'react-hook-form';
|
|
50
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
51
|
+
import { useForm, useWatch } from 'react-hook-form';
|
|
48
52
|
import { z } from 'zod';
|
|
49
53
|
import { formatarData } from '../../_lib/formatters';
|
|
50
54
|
|
|
@@ -64,6 +68,12 @@ type Transfer = {
|
|
|
64
68
|
descricao: string;
|
|
65
69
|
};
|
|
66
70
|
|
|
71
|
+
type TransferDraftPayload = {
|
|
72
|
+
values: TransferFormValues;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const TRANSFER_FORM_DRAFT_STORAGE_KEY = 'finance-transfer-form-draft';
|
|
76
|
+
|
|
67
77
|
const transferFormSchema = z
|
|
68
78
|
.object({
|
|
69
79
|
sourceAccountId: z.string().trim().min(1, 'Conta de origem é obrigatória'),
|
|
@@ -91,7 +101,8 @@ function NovaTransferenciaSheet({
|
|
|
91
101
|
t: ReturnType<typeof useTranslations>;
|
|
92
102
|
onCreated: () => Promise<void> | void;
|
|
93
103
|
}) {
|
|
94
|
-
const { request, showToastHandler } =
|
|
104
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
105
|
+
useApp();
|
|
95
106
|
const [open, setOpen] = useState(false);
|
|
96
107
|
|
|
97
108
|
const form = useForm<TransferFormValues>({
|
|
@@ -105,19 +116,85 @@ function NovaTransferenciaSheet({
|
|
|
105
116
|
},
|
|
106
117
|
});
|
|
107
118
|
|
|
119
|
+
const watchedFormValues = useWatch({
|
|
120
|
+
control: form.control,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const hasDraftContent = useMemo(
|
|
124
|
+
() =>
|
|
125
|
+
Boolean(
|
|
126
|
+
(watchedFormValues.sourceAccountId ?? '').trim() ||
|
|
127
|
+
(watchedFormValues.destinationAccountId ?? '').trim() ||
|
|
128
|
+
(watchedFormValues.description ?? '').trim() ||
|
|
129
|
+
(watchedFormValues.amount ?? 0) > 0 ||
|
|
130
|
+
(watchedFormValues.date ?? '') !== new Date().toISOString().slice(0, 10)
|
|
131
|
+
),
|
|
132
|
+
[watchedFormValues]
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const {
|
|
136
|
+
clearDraft,
|
|
137
|
+
loadDraft,
|
|
138
|
+
hasDraft,
|
|
139
|
+
savedAt: draftSavedAt,
|
|
140
|
+
} = useFormDraft<TransferDraftPayload>({
|
|
141
|
+
storageKey: TRANSFER_FORM_DRAFT_STORAGE_KEY,
|
|
142
|
+
value: {
|
|
143
|
+
values: {
|
|
144
|
+
sourceAccountId: watchedFormValues.sourceAccountId ?? '',
|
|
145
|
+
destinationAccountId: watchedFormValues.destinationAccountId ?? '',
|
|
146
|
+
date: watchedFormValues.date ?? new Date().toISOString().slice(0, 10),
|
|
147
|
+
amount: watchedFormValues.amount ?? 0,
|
|
148
|
+
description: watchedFormValues.description ?? '',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
hasData: hasDraftContent,
|
|
152
|
+
enabled: open,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const draftStatusContent = useMemo(() => {
|
|
156
|
+
if (!hasDraft || !draftSavedAt) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const savedDate = new Date(draftSavedAt);
|
|
161
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
166
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
167
|
+
addSuffix: true,
|
|
168
|
+
locale,
|
|
169
|
+
});
|
|
170
|
+
const absoluteLabel = formatDateTime(
|
|
171
|
+
savedDate,
|
|
172
|
+
getSettingValue,
|
|
173
|
+
currentLocaleCode
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return currentLocaleCode.startsWith('pt')
|
|
177
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
178
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
179
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
180
|
+
|
|
108
181
|
useEffect(() => {
|
|
109
182
|
if (!open) {
|
|
110
183
|
return;
|
|
111
184
|
}
|
|
112
185
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
186
|
+
const storedDraft = loadDraft();
|
|
187
|
+
|
|
188
|
+
form.reset(
|
|
189
|
+
storedDraft?.payload.values ?? {
|
|
190
|
+
sourceAccountId: '',
|
|
191
|
+
destinationAccountId: '',
|
|
192
|
+
date: new Date().toISOString().slice(0, 10),
|
|
193
|
+
amount: 0,
|
|
194
|
+
description: '',
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
}, [form, loadDraft, open]);
|
|
121
198
|
|
|
122
199
|
const handleSubmit = async (values: TransferFormValues) => {
|
|
123
200
|
try {
|
|
@@ -133,6 +210,7 @@ function NovaTransferenciaSheet({
|
|
|
133
210
|
},
|
|
134
211
|
});
|
|
135
212
|
|
|
213
|
+
clearDraft();
|
|
136
214
|
await onCreated();
|
|
137
215
|
setOpen(false);
|
|
138
216
|
showToastHandler?.('success', 'Transferência cadastrada com sucesso');
|
|
@@ -263,17 +341,24 @@ function NovaTransferenciaSheet({
|
|
|
263
341
|
)}
|
|
264
342
|
/>
|
|
265
343
|
|
|
266
|
-
<div className="
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
344
|
+
<div className="pt-4">
|
|
345
|
+
{draftStatusContent ? (
|
|
346
|
+
<p className="mb-3 text-xs text-muted-foreground">
|
|
347
|
+
{draftStatusContent}
|
|
348
|
+
</p>
|
|
349
|
+
) : null}
|
|
350
|
+
<div className="flex justify-end gap-2">
|
|
351
|
+
<Button
|
|
352
|
+
type="button"
|
|
353
|
+
variant="outline"
|
|
354
|
+
onClick={() => setOpen(false)}
|
|
355
|
+
>
|
|
356
|
+
{t('common.cancel')}
|
|
357
|
+
</Button>
|
|
358
|
+
<Button type="submit" disabled={form.formState.isSubmitting}>
|
|
359
|
+
{t('newTransfer.submit')}
|
|
360
|
+
</Button>
|
|
361
|
+
</div>
|
|
277
362
|
</div>
|
|
278
363
|
</form>
|
|
279
364
|
</Form>
|