@hed-hog/finance 0.0.318 → 0.0.319
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/dist/dto/create-bank-account.dto.d.ts +1 -0
- package/dist/dto/create-bank-account.dto.d.ts.map +1 -1
- package/dist/dto/create-bank-account.dto.js +7 -0
- package/dist/dto/create-bank-account.dto.js.map +1 -1
- package/dist/dto/create-bank-statement-entry.dto.d.ts +8 -0
- package/dist/dto/create-bank-statement-entry.dto.d.ts.map +1 -0
- package/dist/dto/create-bank-statement-entry.dto.js +54 -0
- package/dist/dto/create-bank-statement-entry.dto.js.map +1 -0
- package/dist/dto/update-bank-account.dto.d.ts +1 -0
- package/dist/dto/update-bank-account.dto.d.ts.map +1 -1
- package/dist/dto/update-bank-account.dto.js +7 -0
- package/dist/dto/update-bank-account.dto.js.map +1 -1
- package/dist/dto/update-bank-statement-entry.dto.d.ts +6 -0
- package/dist/dto/update-bank-statement-entry.dto.d.ts.map +1 -0
- package/dist/dto/update-bank-statement-entry.dto.js +42 -0
- package/dist/dto/update-bank-statement-entry.dto.js.map +1 -0
- package/dist/finance-bank-accounts.controller.d.ts +25 -13
- package/dist/finance-bank-accounts.controller.d.ts.map +1 -1
- package/dist/finance-bank-accounts.controller.js +5 -3
- package/dist/finance-bank-accounts.controller.js.map +1 -1
- package/dist/finance-data.controller.d.ts +4 -0
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.d.ts +3 -2
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.js +10 -6
- package/dist/finance-installments.controller.js.map +1 -1
- package/dist/finance-statements.controller.d.ts +61 -12
- package/dist/finance-statements.controller.d.ts.map +1 -1
- package/dist/finance-statements.controller.js +50 -8
- package/dist/finance-statements.controller.js.map +1 -1
- package/dist/finance-transfers.controller.d.ts +13 -8
- package/dist/finance-transfers.controller.d.ts.map +1 -1
- package/dist/finance-transfers.controller.js +11 -5
- package/dist/finance-transfers.controller.js.map +1 -1
- package/dist/finance.service.d.ts +124 -35
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +389 -55
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/role.yaml +9 -1
- package/hedhog/data/route.yaml +42 -0
- package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +87 -72
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +53 -25
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +8 -0
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +60 -24
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +114 -31
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +25 -3
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +732 -61
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +101 -15
- package/hedhog/table/bank_statement_line.yaml +1 -1
- package/hedhog/table/cashflow_projection.yaml +1 -1
- package/hedhog/table/financial_installment.yaml +2 -2
- package/hedhog/table/financial_title.yaml +1 -1
- package/hedhog/table/installment_allocation.yaml +1 -1
- package/hedhog/table/receivable_schedule.yaml +1 -1
- package/hedhog/table/settlement.yaml +1 -1
- package/hedhog/table/settlement_allocation.yaml +5 -5
- package/package.json +7 -7
- package/src/dto/create-bank-account.dto.ts +18 -1
- package/src/dto/create-bank-statement-entry.dto.ts +50 -0
- package/src/dto/update-bank-account.dto.ts +11 -1
- package/src/dto/update-bank-statement-entry.dto.ts +31 -0
- package/src/finance-bank-accounts.controller.ts +3 -2
- package/src/finance-installments.controller.ts +9 -3
- package/src/finance-statements.controller.ts +40 -0
- package/src/finance-transfers.controller.ts +7 -1
- package/src/finance.service.ts +543 -61
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { FinancePageSection } from '@/app/(app)/(libraries)/finance/_components/finance-layout';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
EmptyState,
|
|
6
|
+
Page,
|
|
7
|
+
PageHeader,
|
|
8
|
+
PaginationFooter,
|
|
9
|
+
} from '@/components/entity-list';
|
|
5
10
|
import { Button } from '@/components/ui/button';
|
|
11
|
+
import { DateRangePicker } from '@/components/ui/date-range-picker';
|
|
6
12
|
import {
|
|
7
13
|
Dialog,
|
|
8
14
|
DialogContent,
|
|
@@ -57,7 +63,9 @@ import {
|
|
|
57
63
|
ArrowDownRight,
|
|
58
64
|
ArrowUpRight,
|
|
59
65
|
Download,
|
|
66
|
+
Pencil,
|
|
60
67
|
Plus,
|
|
68
|
+
Trash2,
|
|
61
69
|
Upload,
|
|
62
70
|
} from 'lucide-react';
|
|
63
71
|
import { useTranslations } from 'next-intl';
|
|
@@ -74,6 +82,10 @@ type BankAccount = {
|
|
|
74
82
|
saldoAtual: number;
|
|
75
83
|
};
|
|
76
84
|
|
|
85
|
+
type PaginatedBankAccountsResponse = {
|
|
86
|
+
data: BankAccount[];
|
|
87
|
+
};
|
|
88
|
+
|
|
77
89
|
type Statement = {
|
|
78
90
|
id: string;
|
|
79
91
|
contaBancariaId: string;
|
|
@@ -87,14 +99,41 @@ type Statement = {
|
|
|
87
99
|
| 'conciliado'
|
|
88
100
|
| 'estornado'
|
|
89
101
|
| 'ajustado';
|
|
102
|
+
canEdit?: boolean;
|
|
103
|
+
canDelete?: boolean;
|
|
104
|
+
isTransfer?: boolean;
|
|
90
105
|
};
|
|
91
106
|
|
|
107
|
+
type PaginatedStatementsResponse = {
|
|
108
|
+
data: Statement[];
|
|
109
|
+
total: number;
|
|
110
|
+
page: number;
|
|
111
|
+
pageSize: number;
|
|
112
|
+
prev: number | null;
|
|
113
|
+
next: number | null;
|
|
114
|
+
lastPage: number;
|
|
115
|
+
summary?: {
|
|
116
|
+
totalEntradas?: number;
|
|
117
|
+
totalSaidas?: number;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const statementEntrySchema = z.object({
|
|
122
|
+
date: z.string().trim().min(1, 'Data é obrigatória'),
|
|
123
|
+
description: z.string().trim().min(1, 'Descrição é obrigatória'),
|
|
124
|
+
type: z.enum(['entrada', 'saida']),
|
|
125
|
+
amount: z.number().min(0.01, 'Valor deve ser maior que zero'),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
type StatementEntryFormValues = z.infer<typeof statementEntrySchema>;
|
|
129
|
+
|
|
92
130
|
const bankAccountFormSchema = z.object({
|
|
93
131
|
banco: z.string().trim().min(1, 'Banco é obrigatório'),
|
|
94
132
|
agencia: z.string().optional(),
|
|
95
133
|
conta: z.string().optional(),
|
|
96
134
|
tipo: z.string().min(1, 'Tipo é obrigatório'),
|
|
97
135
|
descricao: z.string().optional(),
|
|
136
|
+
dataInicial: z.string().optional(),
|
|
98
137
|
saldoInicial: z.number().min(0, 'Saldo inicial inválido'),
|
|
99
138
|
});
|
|
100
139
|
|
|
@@ -148,6 +187,7 @@ function NovaContaBancariaSheet({
|
|
|
148
187
|
conta: '',
|
|
149
188
|
tipo: '',
|
|
150
189
|
descricao: '',
|
|
190
|
+
dataInicial: new Date().toISOString().slice(0, 10),
|
|
151
191
|
saldoInicial: 0,
|
|
152
192
|
},
|
|
153
193
|
});
|
|
@@ -170,6 +210,9 @@ function NovaContaBancariaSheet({
|
|
|
170
210
|
conta: watchedBankValues.conta ?? '',
|
|
171
211
|
tipo: watchedBankValues.tipo ?? '',
|
|
172
212
|
descricao: watchedBankValues.descricao ?? '',
|
|
213
|
+
dataInicial:
|
|
214
|
+
watchedBankValues.dataInicial ??
|
|
215
|
+
new Date().toISOString().slice(0, 10),
|
|
173
216
|
saldoInicial: watchedBankValues.saldoInicial ?? 0,
|
|
174
217
|
},
|
|
175
218
|
},
|
|
@@ -179,6 +222,8 @@ function NovaContaBancariaSheet({
|
|
|
179
222
|
(watchedBankValues.conta ?? '').trim() ||
|
|
180
223
|
(watchedBankValues.tipo ?? '').trim() ||
|
|
181
224
|
(watchedBankValues.descricao ?? '').trim() ||
|
|
225
|
+
(watchedBankValues.dataInicial ?? '').trim() !==
|
|
226
|
+
new Date().toISOString().slice(0, 10) ||
|
|
182
227
|
(watchedBankValues.saldoInicial ?? 0) > 0
|
|
183
228
|
),
|
|
184
229
|
enabled: open,
|
|
@@ -224,6 +269,7 @@ function NovaContaBancariaSheet({
|
|
|
224
269
|
conta: '',
|
|
225
270
|
tipo: '',
|
|
226
271
|
descricao: '',
|
|
272
|
+
dataInicial: new Date().toISOString().slice(0, 10),
|
|
227
273
|
saldoInicial: 0,
|
|
228
274
|
}
|
|
229
275
|
);
|
|
@@ -240,6 +286,7 @@ function NovaContaBancariaSheet({
|
|
|
240
286
|
account: values.conta || undefined,
|
|
241
287
|
type: values.tipo,
|
|
242
288
|
description: values.descricao?.trim() || undefined,
|
|
289
|
+
start_date: values.dataInicial || undefined,
|
|
243
290
|
initial_balance: values.saldoInicial,
|
|
244
291
|
},
|
|
245
292
|
});
|
|
@@ -370,6 +417,28 @@ function NovaContaBancariaSheet({
|
|
|
370
417
|
)}
|
|
371
418
|
/>
|
|
372
419
|
|
|
420
|
+
<FormField
|
|
421
|
+
control={form.control}
|
|
422
|
+
name="dataInicial"
|
|
423
|
+
render={({ field }) => (
|
|
424
|
+
<FormItem>
|
|
425
|
+
<FormLabel>Data inicial</FormLabel>
|
|
426
|
+
<FormControl>
|
|
427
|
+
<Input
|
|
428
|
+
type="date"
|
|
429
|
+
{...field}
|
|
430
|
+
value={
|
|
431
|
+
field.value || new Date().toISOString().slice(0, 10)
|
|
432
|
+
}
|
|
433
|
+
/>
|
|
434
|
+
</FormControl>
|
|
435
|
+
<FormMessage />
|
|
436
|
+
</FormItem>
|
|
437
|
+
)}
|
|
438
|
+
/>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<div className="grid grid-cols-2 gap-4">
|
|
373
442
|
<FormField
|
|
374
443
|
control={form.control}
|
|
375
444
|
name="saldoInicial"
|
|
@@ -390,25 +459,25 @@ function NovaContaBancariaSheet({
|
|
|
390
459
|
</FormItem>
|
|
391
460
|
)}
|
|
392
461
|
/>
|
|
393
|
-
</div>
|
|
394
462
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
463
|
+
<FormField
|
|
464
|
+
control={form.control}
|
|
465
|
+
name="descricao"
|
|
466
|
+
render={({ field }) => (
|
|
467
|
+
<FormItem>
|
|
468
|
+
<FormLabel>{tBank('fields.description')}</FormLabel>
|
|
469
|
+
<FormControl>
|
|
470
|
+
<Input
|
|
471
|
+
placeholder={tBank('fields.descriptionPlaceholder')}
|
|
472
|
+
{...field}
|
|
473
|
+
value={field.value || ''}
|
|
474
|
+
/>
|
|
475
|
+
</FormControl>
|
|
476
|
+
<FormMessage />
|
|
477
|
+
</FormItem>
|
|
478
|
+
)}
|
|
479
|
+
/>
|
|
480
|
+
</div>
|
|
412
481
|
|
|
413
482
|
{draftStatusContent ? (
|
|
414
483
|
<p className="text-xs text-muted-foreground">
|
|
@@ -681,6 +750,179 @@ function ImportarExtratoSheet({
|
|
|
681
750
|
);
|
|
682
751
|
}
|
|
683
752
|
|
|
753
|
+
function NovoLancamentoSheet({
|
|
754
|
+
open,
|
|
755
|
+
onOpenChange,
|
|
756
|
+
bankAccountId,
|
|
757
|
+
onCreated,
|
|
758
|
+
}: {
|
|
759
|
+
open: boolean;
|
|
760
|
+
onOpenChange: (open: boolean) => void;
|
|
761
|
+
bankAccountId?: string;
|
|
762
|
+
onCreated: () => Promise<void> | void;
|
|
763
|
+
}) {
|
|
764
|
+
const { request, showToastHandler } = useApp();
|
|
765
|
+
const form = useForm<StatementEntryFormValues>({
|
|
766
|
+
resolver: zodResolver(statementEntrySchema),
|
|
767
|
+
defaultValues: {
|
|
768
|
+
date: new Date().toISOString().slice(0, 10),
|
|
769
|
+
description: '',
|
|
770
|
+
type: 'entrada',
|
|
771
|
+
amount: 0,
|
|
772
|
+
},
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
useEffect(() => {
|
|
776
|
+
if (open) {
|
|
777
|
+
form.reset({
|
|
778
|
+
date: new Date().toISOString().slice(0, 10),
|
|
779
|
+
description: '',
|
|
780
|
+
type: 'entrada',
|
|
781
|
+
amount: 0,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
}, [form, open]);
|
|
785
|
+
|
|
786
|
+
const handleSubmit = async (values: StatementEntryFormValues) => {
|
|
787
|
+
if (!bankAccountId) {
|
|
788
|
+
showToastHandler?.('error', 'Selecione uma conta bancária');
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
try {
|
|
793
|
+
await request({
|
|
794
|
+
url: '/finance/statements',
|
|
795
|
+
method: 'POST',
|
|
796
|
+
data: {
|
|
797
|
+
bank_account_id: Number(bankAccountId),
|
|
798
|
+
date: values.date,
|
|
799
|
+
description: values.description.trim(),
|
|
800
|
+
type: values.type,
|
|
801
|
+
amount: values.amount,
|
|
802
|
+
},
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
await onCreated();
|
|
806
|
+
onOpenChange(false);
|
|
807
|
+
showToastHandler?.('success', 'Movimentação adicionada com sucesso');
|
|
808
|
+
} catch (error: any) {
|
|
809
|
+
const message = error?.response?.data?.message;
|
|
810
|
+
showToastHandler?.(
|
|
811
|
+
'error',
|
|
812
|
+
typeof message === 'string' && message.trim()
|
|
813
|
+
? message
|
|
814
|
+
: 'Não foi possível adicionar a movimentação'
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
return (
|
|
820
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
821
|
+
<SheetContent className="w-full sm:max-w-lg overflow-y-auto">
|
|
822
|
+
<SheetHeader>
|
|
823
|
+
<SheetTitle>Novo lançamento</SheetTitle>
|
|
824
|
+
<SheetDescription>
|
|
825
|
+
Adicione uma movimentação manual ao extrato da conta bancária.
|
|
826
|
+
</SheetDescription>
|
|
827
|
+
</SheetHeader>
|
|
828
|
+
|
|
829
|
+
<Form {...form}>
|
|
830
|
+
<form
|
|
831
|
+
className="space-y-4 px-4"
|
|
832
|
+
onSubmit={form.handleSubmit(handleSubmit)}
|
|
833
|
+
>
|
|
834
|
+
<div className="grid grid-cols-2 gap-4">
|
|
835
|
+
<FormField
|
|
836
|
+
control={form.control}
|
|
837
|
+
name="date"
|
|
838
|
+
render={({ field }) => (
|
|
839
|
+
<FormItem>
|
|
840
|
+
<FormLabel>Data</FormLabel>
|
|
841
|
+
<FormControl>
|
|
842
|
+
<Input type="date" {...field} />
|
|
843
|
+
</FormControl>
|
|
844
|
+
<FormMessage />
|
|
845
|
+
</FormItem>
|
|
846
|
+
)}
|
|
847
|
+
/>
|
|
848
|
+
|
|
849
|
+
<FormField
|
|
850
|
+
control={form.control}
|
|
851
|
+
name="type"
|
|
852
|
+
render={({ field }) => (
|
|
853
|
+
<FormItem>
|
|
854
|
+
<FormLabel>Tipo</FormLabel>
|
|
855
|
+
<Select value={field.value} onValueChange={field.onChange}>
|
|
856
|
+
<FormControl>
|
|
857
|
+
<SelectTrigger className="w-full">
|
|
858
|
+
<SelectValue />
|
|
859
|
+
</SelectTrigger>
|
|
860
|
+
</FormControl>
|
|
861
|
+
<SelectContent>
|
|
862
|
+
<SelectItem value="entrada">Entrada</SelectItem>
|
|
863
|
+
<SelectItem value="saida">Saída</SelectItem>
|
|
864
|
+
</SelectContent>
|
|
865
|
+
</Select>
|
|
866
|
+
<FormMessage />
|
|
867
|
+
</FormItem>
|
|
868
|
+
)}
|
|
869
|
+
/>
|
|
870
|
+
</div>
|
|
871
|
+
|
|
872
|
+
<FormField
|
|
873
|
+
control={form.control}
|
|
874
|
+
name="description"
|
|
875
|
+
render={({ field }) => (
|
|
876
|
+
<FormItem>
|
|
877
|
+
<FormLabel>Descrição</FormLabel>
|
|
878
|
+
<FormControl>
|
|
879
|
+
<Input {...field} />
|
|
880
|
+
</FormControl>
|
|
881
|
+
<FormMessage />
|
|
882
|
+
</FormItem>
|
|
883
|
+
)}
|
|
884
|
+
/>
|
|
885
|
+
|
|
886
|
+
<FormField
|
|
887
|
+
control={form.control}
|
|
888
|
+
name="amount"
|
|
889
|
+
render={({ field }) => (
|
|
890
|
+
<FormItem>
|
|
891
|
+
<FormLabel>Valor</FormLabel>
|
|
892
|
+
<FormControl>
|
|
893
|
+
<InputMoney
|
|
894
|
+
ref={field.ref}
|
|
895
|
+
name={field.name}
|
|
896
|
+
value={field.value}
|
|
897
|
+
onBlur={field.onBlur}
|
|
898
|
+
onValueChange={(value) => field.onChange(value ?? 0)}
|
|
899
|
+
placeholder="0,00"
|
|
900
|
+
/>
|
|
901
|
+
</FormControl>
|
|
902
|
+
<FormMessage />
|
|
903
|
+
</FormItem>
|
|
904
|
+
)}
|
|
905
|
+
/>
|
|
906
|
+
|
|
907
|
+
<div className="flex justify-end gap-2 pt-2">
|
|
908
|
+
<Button
|
|
909
|
+
type="button"
|
|
910
|
+
variant="outline"
|
|
911
|
+
onClick={() => onOpenChange(false)}
|
|
912
|
+
>
|
|
913
|
+
Cancelar
|
|
914
|
+
</Button>
|
|
915
|
+
<Button type="submit" disabled={form.formState.isSubmitting}>
|
|
916
|
+
Salvar
|
|
917
|
+
</Button>
|
|
918
|
+
</div>
|
|
919
|
+
</form>
|
|
920
|
+
</Form>
|
|
921
|
+
</SheetContent>
|
|
922
|
+
</Sheet>
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
|
|
684
926
|
export default function ExtratosPage() {
|
|
685
927
|
const t = useTranslations('finance.StatementsPage');
|
|
686
928
|
const { request, showToastHandler } = useApp();
|
|
@@ -695,8 +937,29 @@ export default function ExtratosPage() {
|
|
|
695
937
|
const [search, setSearch] = useState('');
|
|
696
938
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
697
939
|
const [isImportSheetOpen, setIsImportSheetOpen] = useState(false);
|
|
940
|
+
const [isNewEntrySheetOpen, setIsNewEntrySheetOpen] = useState(false);
|
|
698
941
|
const [extratoSelecionado, setExtratoSelecionado] =
|
|
699
942
|
useState<Statement | null>(null);
|
|
943
|
+
const [fromDate, setFromDate] = useState('');
|
|
944
|
+
const [toDate, setToDate] = useState('');
|
|
945
|
+
const [page, setPage] = useState(1);
|
|
946
|
+
// Inline editing state — keyed by statement id
|
|
947
|
+
const [editingValueId, setEditingValueId] = useState<string | null>(null);
|
|
948
|
+
const [editingValue, setEditingValue] = useState(0);
|
|
949
|
+
const [editingDescId, setEditingDescId] = useState<string | null>(null);
|
|
950
|
+
const [editingDesc, setEditingDesc] = useState('');
|
|
951
|
+
const [editingDateId, setEditingDateId] = useState<string | null>(null);
|
|
952
|
+
const [editingDate, setEditingDate] = useState('');
|
|
953
|
+
// Pending edits to save
|
|
954
|
+
const [pendingEdits, setPendingEdits] = useState<
|
|
955
|
+
Map<string, { amount?: number; description?: string; date?: string }>
|
|
956
|
+
>(new Map());
|
|
957
|
+
const [isSavingEdits, setIsSavingEdits] = useState(false);
|
|
958
|
+
const [statementToDelete, setStatementToDelete] = useState<Statement | null>(
|
|
959
|
+
null
|
|
960
|
+
);
|
|
961
|
+
const [isDeletingStatement, setIsDeletingStatement] = useState(false);
|
|
962
|
+
const pageSize = 10;
|
|
700
963
|
|
|
701
964
|
useEffect(() => {
|
|
702
965
|
const timeoutId = window.setTimeout(() => {
|
|
@@ -715,9 +978,20 @@ export default function ExtratosPage() {
|
|
|
715
978
|
const response = await request({
|
|
716
979
|
url: '/finance/bank-accounts',
|
|
717
980
|
method: 'GET',
|
|
981
|
+
params: {
|
|
982
|
+
page: 1,
|
|
983
|
+
pageSize: 100,
|
|
984
|
+
},
|
|
718
985
|
});
|
|
719
986
|
|
|
720
|
-
|
|
987
|
+
const payload = response?.data as
|
|
988
|
+
| { data?: BankAccount[] }
|
|
989
|
+
| BankAccount[]
|
|
990
|
+
| undefined;
|
|
991
|
+
|
|
992
|
+
return (
|
|
993
|
+
Array.isArray(payload) ? payload : payload?.data || []
|
|
994
|
+
) as BankAccount[];
|
|
721
995
|
},
|
|
722
996
|
});
|
|
723
997
|
|
|
@@ -769,44 +1043,98 @@ export default function ExtratosPage() {
|
|
|
769
1043
|
router.replace(`${pathname}?${params.toString()}`);
|
|
770
1044
|
}, [activeContaFilter, bankAccountIdFromUrl, pathname, router, searchParams]);
|
|
771
1045
|
|
|
772
|
-
const { data:
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1046
|
+
const { data: extratosResponse, refetch: refetchExtratos } =
|
|
1047
|
+
useQuery<PaginatedStatementsResponse>({
|
|
1048
|
+
queryKey: [
|
|
1049
|
+
'finance-statements',
|
|
1050
|
+
activeContaFilter,
|
|
1051
|
+
debouncedSearch,
|
|
1052
|
+
fromDate,
|
|
1053
|
+
toDate,
|
|
1054
|
+
page,
|
|
1055
|
+
pageSize,
|
|
1056
|
+
],
|
|
1057
|
+
queryFn: async () => {
|
|
1058
|
+
if (!activeContaFilter) {
|
|
1059
|
+
return {
|
|
1060
|
+
data: [],
|
|
1061
|
+
total: 0,
|
|
1062
|
+
page,
|
|
1063
|
+
pageSize,
|
|
1064
|
+
prev: null,
|
|
1065
|
+
next: null,
|
|
1066
|
+
lastPage: 1,
|
|
1067
|
+
summary: {
|
|
1068
|
+
totalEntradas: 0,
|
|
1069
|
+
totalSaidas: 0,
|
|
1070
|
+
},
|
|
1071
|
+
} as PaginatedStatementsResponse;
|
|
1072
|
+
}
|
|
780
1073
|
|
|
781
|
-
|
|
782
|
-
|
|
1074
|
+
const params: Record<string, string | number> = {
|
|
1075
|
+
bank_account_id: activeContaFilter,
|
|
1076
|
+
page,
|
|
1077
|
+
pageSize,
|
|
1078
|
+
};
|
|
783
1079
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1080
|
+
const trimmedSearch = debouncedSearch.trim();
|
|
1081
|
+
if (trimmedSearch) {
|
|
1082
|
+
params.search = trimmedSearch;
|
|
1083
|
+
}
|
|
788
1084
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
});
|
|
1085
|
+
if (fromDate) {
|
|
1086
|
+
params.from = fromDate;
|
|
1087
|
+
}
|
|
793
1088
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1089
|
+
if (toDate) {
|
|
1090
|
+
params.to = toDate;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const response = await request({
|
|
1094
|
+
url: '/finance/statements',
|
|
1095
|
+
method: 'GET',
|
|
1096
|
+
params,
|
|
1097
|
+
});
|
|
797
1098
|
|
|
1099
|
+
return (response?.data || {
|
|
1100
|
+
data: [],
|
|
1101
|
+
total: 0,
|
|
1102
|
+
page,
|
|
1103
|
+
pageSize,
|
|
1104
|
+
prev: null,
|
|
1105
|
+
next: null,
|
|
1106
|
+
lastPage: 1,
|
|
1107
|
+
summary: {
|
|
1108
|
+
totalEntradas: 0,
|
|
1109
|
+
totalSaidas: 0,
|
|
1110
|
+
},
|
|
1111
|
+
}) as PaginatedStatementsResponse;
|
|
1112
|
+
},
|
|
1113
|
+
placeholderData: (old) => old,
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
useEffect(() => {
|
|
1117
|
+
setPage(1);
|
|
1118
|
+
}, [activeContaFilter, debouncedSearch, fromDate, toDate]);
|
|
1119
|
+
|
|
1120
|
+
const extratos = extratosResponse?.data ?? [];
|
|
1121
|
+
const totalExtratos = extratosResponse?.total ?? 0;
|
|
798
1122
|
const conta = contasBancarias.find((item) => item.id === activeContaFilter);
|
|
799
1123
|
const contaExtratoSelecionado = extratoSelecionado
|
|
800
1124
|
? contasBancarias.find(
|
|
801
1125
|
(item) => item.id === extratoSelecionado.contaBancariaId
|
|
802
1126
|
)
|
|
803
1127
|
: undefined;
|
|
804
|
-
const totalEntradas =
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1128
|
+
const totalEntradas =
|
|
1129
|
+
extratosResponse?.summary?.totalEntradas ??
|
|
1130
|
+
extratos
|
|
1131
|
+
.filter((statement) => statement.tipo === 'entrada')
|
|
1132
|
+
.reduce((acc, statement) => acc + statement.valor, 0);
|
|
1133
|
+
const totalSaidas =
|
|
1134
|
+
extratosResponse?.summary?.totalSaidas ??
|
|
1135
|
+
extratos
|
|
1136
|
+
.filter((statement) => statement.tipo === 'saida')
|
|
1137
|
+
.reduce((acc, statement) => acc + statement.valor, 0);
|
|
810
1138
|
const summaryCards = [
|
|
811
1139
|
{
|
|
812
1140
|
key: 'inflows',
|
|
@@ -836,7 +1164,6 @@ export default function ExtratosPage() {
|
|
|
836
1164
|
layout: 'compact' as const,
|
|
837
1165
|
},
|
|
838
1166
|
];
|
|
839
|
-
|
|
840
1167
|
const handleExport = async () => {
|
|
841
1168
|
if (!activeContaFilter) {
|
|
842
1169
|
showToastHandler?.('error', 'Selecione uma conta bancária para exportar');
|
|
@@ -852,6 +1179,14 @@ export default function ExtratosPage() {
|
|
|
852
1179
|
params.set('search', trimmedSearch);
|
|
853
1180
|
}
|
|
854
1181
|
|
|
1182
|
+
if (fromDate) {
|
|
1183
|
+
params.set('from', fromDate);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
if (toDate) {
|
|
1187
|
+
params.set('to', toDate);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
855
1190
|
const response = await request<Blob>({
|
|
856
1191
|
url: `/finance/statements/export?${params.toString()}`,
|
|
857
1192
|
method: 'GET',
|
|
@@ -886,6 +1221,136 @@ export default function ExtratosPage() {
|
|
|
886
1221
|
}
|
|
887
1222
|
};
|
|
888
1223
|
|
|
1224
|
+
const handleStartInlineEdit = (extrato: Statement) => {
|
|
1225
|
+
if (!extrato.canEdit) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
setEditingValueId(extrato.id);
|
|
1230
|
+
setEditingValue(Math.abs(Number(extrato.valor || 0)));
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
const handleChangeInlineValue = (extrato: Statement, value: number) => {
|
|
1234
|
+
setEditingValue(value);
|
|
1235
|
+
setPendingEdits((prev) => {
|
|
1236
|
+
const next = new Map(prev);
|
|
1237
|
+
const existing = next.get(extrato.id) ?? {};
|
|
1238
|
+
next.set(extrato.id, { ...existing, amount: value });
|
|
1239
|
+
return next;
|
|
1240
|
+
});
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
const handleStartInlineEditDesc = (extrato: Statement) => {
|
|
1244
|
+
if (!extrato.canEdit) return;
|
|
1245
|
+
setEditingDescId(extrato.id);
|
|
1246
|
+
setEditingDesc(extrato.descricao);
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
const handleChangeInlineDesc = (extrato: Statement, value: string) => {
|
|
1250
|
+
setEditingDesc(value);
|
|
1251
|
+
setPendingEdits((prev) => {
|
|
1252
|
+
const next = new Map(prev);
|
|
1253
|
+
const existing = next.get(extrato.id) ?? {};
|
|
1254
|
+
next.set(extrato.id, { ...existing, description: value });
|
|
1255
|
+
return next;
|
|
1256
|
+
});
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
const handleStartInlineEditDate = (extrato: Statement) => {
|
|
1260
|
+
if (!extrato.canEdit) return;
|
|
1261
|
+
setEditingDateId(extrato.id);
|
|
1262
|
+
setEditingDate(extrato.data.slice(0, 10));
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
const handleChangeInlineDate = (extrato: Statement, value: string) => {
|
|
1266
|
+
setEditingDate(value);
|
|
1267
|
+
setPendingEdits((prev) => {
|
|
1268
|
+
const next = new Map(prev);
|
|
1269
|
+
const existing = next.get(extrato.id) ?? {};
|
|
1270
|
+
next.set(extrato.id, { ...existing, date: value });
|
|
1271
|
+
return next;
|
|
1272
|
+
});
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
const handleCancelPendingEdits = () => {
|
|
1276
|
+
setPendingEdits(new Map());
|
|
1277
|
+
setEditingValueId(null);
|
|
1278
|
+
setEditingDescId(null);
|
|
1279
|
+
setEditingDateId(null);
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
const handleSaveAllPendingEdits = async () => {
|
|
1283
|
+
if (pendingEdits.size === 0) return;
|
|
1284
|
+
setIsSavingEdits(true);
|
|
1285
|
+
let hasError = false;
|
|
1286
|
+
try {
|
|
1287
|
+
for (const [id, edits] of pendingEdits.entries()) {
|
|
1288
|
+
await request({
|
|
1289
|
+
url: `/finance/statements/${id}`,
|
|
1290
|
+
method: 'PATCH',
|
|
1291
|
+
data: edits,
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
} catch (error: any) {
|
|
1295
|
+
hasError = true;
|
|
1296
|
+
const message = error?.response?.data?.message;
|
|
1297
|
+
showToastHandler?.(
|
|
1298
|
+
'error',
|
|
1299
|
+
typeof message === 'string' && message.trim()
|
|
1300
|
+
? message
|
|
1301
|
+
: 'Não foi possível salvar as alterações'
|
|
1302
|
+
);
|
|
1303
|
+
} finally {
|
|
1304
|
+
setIsSavingEdits(false);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (!hasError) {
|
|
1308
|
+
setPendingEdits(new Map());
|
|
1309
|
+
setEditingValueId(null);
|
|
1310
|
+
setEditingDescId(null);
|
|
1311
|
+
setEditingDateId(null);
|
|
1312
|
+
await Promise.all([refetchExtratos(), refetchContasBancarias()]);
|
|
1313
|
+
showToastHandler?.('success', 'Alterações salvas com sucesso');
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
const handleDeleteStatement = async (extrato: Statement) => {
|
|
1318
|
+
if (!extrato.canDelete) {
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
setStatementToDelete(extrato);
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
const confirmDeleteStatement = async () => {
|
|
1326
|
+
if (!statementToDelete) return;
|
|
1327
|
+
setIsDeletingStatement(true);
|
|
1328
|
+
try {
|
|
1329
|
+
await request({
|
|
1330
|
+
url: `/finance/statements/${statementToDelete.id}`,
|
|
1331
|
+
method: 'DELETE',
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
if (extratoSelecionado?.id === statementToDelete.id) {
|
|
1335
|
+
setExtratoSelecionado(null);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
setStatementToDelete(null);
|
|
1339
|
+
await Promise.all([refetchExtratos(), refetchContasBancarias()]);
|
|
1340
|
+
showToastHandler?.('success', 'Movimentação excluída com sucesso');
|
|
1341
|
+
} catch (error: any) {
|
|
1342
|
+
const message = error?.response?.data?.message;
|
|
1343
|
+
showToastHandler?.(
|
|
1344
|
+
'error',
|
|
1345
|
+
typeof message === 'string' && message.trim()
|
|
1346
|
+
? message
|
|
1347
|
+
: 'Não foi possível excluir a movimentação'
|
|
1348
|
+
);
|
|
1349
|
+
} finally {
|
|
1350
|
+
setIsDeletingStatement(false);
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
|
|
889
1354
|
return (
|
|
890
1355
|
<Page>
|
|
891
1356
|
<PageHeader
|
|
@@ -902,6 +1367,10 @@ export default function ExtratosPage() {
|
|
|
902
1367
|
<Download className="mr-2 h-4 w-4" />
|
|
903
1368
|
{t('actions.export')}
|
|
904
1369
|
</Button>
|
|
1370
|
+
<Button onClick={() => setIsNewEntrySheetOpen(true)}>
|
|
1371
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
1372
|
+
Novo lançamento
|
|
1373
|
+
</Button>
|
|
905
1374
|
<ImportarExtratoSheet
|
|
906
1375
|
contasBancarias={contasBancarias}
|
|
907
1376
|
t={t}
|
|
@@ -915,6 +1384,15 @@ export default function ExtratosPage() {
|
|
|
915
1384
|
}
|
|
916
1385
|
/>
|
|
917
1386
|
|
|
1387
|
+
<NovoLancamentoSheet
|
|
1388
|
+
open={isNewEntrySheetOpen}
|
|
1389
|
+
onOpenChange={setIsNewEntrySheetOpen}
|
|
1390
|
+
bankAccountId={activeContaFilter}
|
|
1391
|
+
onCreated={async () => {
|
|
1392
|
+
await Promise.all([refetchExtratos(), refetchContasBancarias()]);
|
|
1393
|
+
}}
|
|
1394
|
+
/>
|
|
1395
|
+
|
|
918
1396
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
|
|
919
1397
|
<Select
|
|
920
1398
|
value={activeContaFilter}
|
|
@@ -944,6 +1422,13 @@ export default function ExtratosPage() {
|
|
|
944
1422
|
onSearchChange={setSearch}
|
|
945
1423
|
/>
|
|
946
1424
|
</div>
|
|
1425
|
+
<DateRangePicker
|
|
1426
|
+
fromDate={fromDate}
|
|
1427
|
+
toDate={toDate}
|
|
1428
|
+
onFromDateChange={setFromDate}
|
|
1429
|
+
onToDateChange={setToDate}
|
|
1430
|
+
defaultPreset="thisMonth"
|
|
1431
|
+
/>
|
|
947
1432
|
</div>
|
|
948
1433
|
|
|
949
1434
|
<KpiCardsGrid items={summaryCards} columns={3} />
|
|
@@ -951,10 +1436,10 @@ export default function ExtratosPage() {
|
|
|
951
1436
|
<FinancePageSection
|
|
952
1437
|
variant="flat"
|
|
953
1438
|
title={t('table.title')}
|
|
954
|
-
description={t('table.foundTransactions', { count:
|
|
1439
|
+
description={t('table.foundTransactions', { count: totalExtratos })}
|
|
955
1440
|
contentClassName="p-4 sm:p-5"
|
|
956
1441
|
>
|
|
957
|
-
{
|
|
1442
|
+
{totalExtratos > 0 ? (
|
|
958
1443
|
<div className="overflow-x-auto">
|
|
959
1444
|
<Table className="min-w-190 table-fixed">
|
|
960
1445
|
<TableHeader>
|
|
@@ -972,6 +1457,7 @@ export default function ExtratosPage() {
|
|
|
972
1457
|
<TableHead className="w-35">
|
|
973
1458
|
{t('table.headers.reconciliation')}
|
|
974
1459
|
</TableHead>
|
|
1460
|
+
<TableHead className="w-24 text-right">Ações</TableHead>
|
|
975
1461
|
</TableRow>
|
|
976
1462
|
</TableHeader>
|
|
977
1463
|
<TableBody>
|
|
@@ -989,20 +1475,123 @@ export default function ExtratosPage() {
|
|
|
989
1475
|
role="button"
|
|
990
1476
|
tabIndex={0}
|
|
991
1477
|
>
|
|
992
|
-
<TableCell>
|
|
1478
|
+
<TableCell>
|
|
1479
|
+
{editingDateId === extrato.id ? (
|
|
1480
|
+
<Input
|
|
1481
|
+
type="date"
|
|
1482
|
+
className="h-7 px-1 text-sm"
|
|
1483
|
+
value={editingDate}
|
|
1484
|
+
onChange={(e) =>
|
|
1485
|
+
handleChangeInlineDate(extrato, e.target.value)
|
|
1486
|
+
}
|
|
1487
|
+
onKeyDown={(e) => {
|
|
1488
|
+
e.stopPropagation();
|
|
1489
|
+
if (e.key === 'Escape') setEditingDateId(null);
|
|
1490
|
+
if (e.key === 'Enter') {
|
|
1491
|
+
e.preventDefault();
|
|
1492
|
+
setEditingDateId(null);
|
|
1493
|
+
handleSaveAllPendingEdits();
|
|
1494
|
+
}
|
|
1495
|
+
}}
|
|
1496
|
+
onClick={(e) => e.stopPropagation()}
|
|
1497
|
+
autoFocus
|
|
1498
|
+
/>
|
|
1499
|
+
) : (
|
|
1500
|
+
<button
|
|
1501
|
+
type="button"
|
|
1502
|
+
className={`inline-flex items-center gap-1 ${extrato.canEdit ? 'cursor-pointer underline-offset-2 hover:underline' : 'cursor-default'}`}
|
|
1503
|
+
onClick={(e) => {
|
|
1504
|
+
e.stopPropagation();
|
|
1505
|
+
handleStartInlineEditDate(extrato);
|
|
1506
|
+
}}
|
|
1507
|
+
>
|
|
1508
|
+
{extrato.canEdit ? (
|
|
1509
|
+
<Pencil className="h-3 w-3" />
|
|
1510
|
+
) : null}
|
|
1511
|
+
{formatarData(extrato.data)}
|
|
1512
|
+
</button>
|
|
1513
|
+
)}
|
|
1514
|
+
</TableCell>
|
|
993
1515
|
<TableCell className="truncate" title={extrato.descricao}>
|
|
994
|
-
{extrato.
|
|
1516
|
+
{editingDescId === extrato.id ? (
|
|
1517
|
+
<Input
|
|
1518
|
+
className="h-7 px-1 text-sm"
|
|
1519
|
+
value={editingDesc}
|
|
1520
|
+
onChange={(e) =>
|
|
1521
|
+
handleChangeInlineDesc(extrato, e.target.value)
|
|
1522
|
+
}
|
|
1523
|
+
onKeyDown={(e) => {
|
|
1524
|
+
e.stopPropagation();
|
|
1525
|
+
if (e.key === 'Escape') setEditingDescId(null);
|
|
1526
|
+
if (e.key === 'Enter') {
|
|
1527
|
+
e.preventDefault();
|
|
1528
|
+
setEditingDescId(null);
|
|
1529
|
+
handleSaveAllPendingEdits();
|
|
1530
|
+
}
|
|
1531
|
+
}}
|
|
1532
|
+
onClick={(e) => e.stopPropagation()}
|
|
1533
|
+
autoFocus
|
|
1534
|
+
/>
|
|
1535
|
+
) : (
|
|
1536
|
+
<button
|
|
1537
|
+
type="button"
|
|
1538
|
+
className={`inline-flex max-w-full items-center gap-1 truncate ${extrato.canEdit ? 'cursor-pointer underline-offset-2 hover:underline' : 'cursor-default'}`}
|
|
1539
|
+
onClick={(e) => {
|
|
1540
|
+
e.stopPropagation();
|
|
1541
|
+
handleStartInlineEditDesc(extrato);
|
|
1542
|
+
}}
|
|
1543
|
+
title={extrato.descricao}
|
|
1544
|
+
>
|
|
1545
|
+
{extrato.canEdit ? (
|
|
1546
|
+
<Pencil className="h-3 w-3 shrink-0" />
|
|
1547
|
+
) : null}
|
|
1548
|
+
<span className="truncate">{extrato.descricao}</span>
|
|
1549
|
+
</button>
|
|
1550
|
+
)}
|
|
995
1551
|
</TableCell>
|
|
996
1552
|
<TableCell className="text-right">
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1553
|
+
{editingValueId === extrato.id ? (
|
|
1554
|
+
<InputMoney
|
|
1555
|
+
value={editingValue}
|
|
1556
|
+
onValueChange={(value) =>
|
|
1557
|
+
handleChangeInlineValue(extrato, Number(value || 0))
|
|
1558
|
+
}
|
|
1559
|
+
onKeyDown={(event) => {
|
|
1560
|
+
event.stopPropagation();
|
|
1561
|
+
if (event.key === 'Escape') {
|
|
1562
|
+
setEditingValueId(null);
|
|
1563
|
+
}
|
|
1564
|
+
if (event.key === 'Enter') {
|
|
1565
|
+
event.preventDefault();
|
|
1566
|
+
setEditingValueId(null);
|
|
1567
|
+
handleSaveAllPendingEdits();
|
|
1568
|
+
}
|
|
1569
|
+
}}
|
|
1570
|
+
autoFocus
|
|
1571
|
+
/>
|
|
1572
|
+
) : (
|
|
1573
|
+
<button
|
|
1574
|
+
type="button"
|
|
1575
|
+
className={`inline-flex items-center gap-1 ${
|
|
1576
|
+
extrato.canEdit
|
|
1577
|
+
? 'cursor-pointer underline-offset-2 hover:underline'
|
|
1578
|
+
: 'cursor-default'
|
|
1579
|
+
} ${
|
|
1580
|
+
extrato.tipo === 'entrada'
|
|
1581
|
+
? 'text-green-600'
|
|
1582
|
+
: 'text-red-600'
|
|
1583
|
+
}`}
|
|
1584
|
+
onClick={(event) => {
|
|
1585
|
+
event.stopPropagation();
|
|
1586
|
+
handleStartInlineEdit(extrato);
|
|
1587
|
+
}}
|
|
1588
|
+
>
|
|
1589
|
+
{extrato.canEdit ? (
|
|
1590
|
+
<Pencil className="h-3 w-3" />
|
|
1591
|
+
) : null}
|
|
1592
|
+
<Money value={extrato.valor} />
|
|
1593
|
+
</button>
|
|
1594
|
+
)}
|
|
1006
1595
|
</TableCell>
|
|
1007
1596
|
<TableCell>
|
|
1008
1597
|
{extrato.tipo === 'entrada' ? (
|
|
@@ -1023,6 +1612,21 @@ export default function ExtratosPage() {
|
|
|
1023
1612
|
type="conciliacao"
|
|
1024
1613
|
/>
|
|
1025
1614
|
</TableCell>
|
|
1615
|
+
<TableCell className="text-right">
|
|
1616
|
+
{extrato.canDelete ? (
|
|
1617
|
+
<Button
|
|
1618
|
+
type="button"
|
|
1619
|
+
variant="ghost"
|
|
1620
|
+
size="icon"
|
|
1621
|
+
onClick={(event) => {
|
|
1622
|
+
event.stopPropagation();
|
|
1623
|
+
void handleDeleteStatement(extrato);
|
|
1624
|
+
}}
|
|
1625
|
+
>
|
|
1626
|
+
<Trash2 className="h-4 w-4" />
|
|
1627
|
+
</Button>
|
|
1628
|
+
) : null}
|
|
1629
|
+
</TableCell>
|
|
1026
1630
|
</TableRow>
|
|
1027
1631
|
))}
|
|
1028
1632
|
</TableBody>
|
|
@@ -1037,6 +1641,18 @@ export default function ExtratosPage() {
|
|
|
1037
1641
|
onAction={() => setIsImportSheetOpen(true)}
|
|
1038
1642
|
/>
|
|
1039
1643
|
)}
|
|
1644
|
+
{totalExtratos > 0 ? (
|
|
1645
|
+
<div className="border-t border-border/50 px-4 py-4 sm:px-6">
|
|
1646
|
+
<PaginationFooter
|
|
1647
|
+
currentPage={page}
|
|
1648
|
+
pageSize={pageSize}
|
|
1649
|
+
totalItems={totalExtratos}
|
|
1650
|
+
onPageChange={setPage}
|
|
1651
|
+
onPageSizeChange={() => undefined}
|
|
1652
|
+
pageSizeOptions={[10]}
|
|
1653
|
+
/>
|
|
1654
|
+
</div>
|
|
1655
|
+
) : null}
|
|
1040
1656
|
</FinancePageSection>
|
|
1041
1657
|
|
|
1042
1658
|
<Dialog
|
|
@@ -1139,6 +1755,61 @@ export default function ExtratosPage() {
|
|
|
1139
1755
|
</DialogFooter>
|
|
1140
1756
|
</DialogContent>
|
|
1141
1757
|
</Dialog>
|
|
1758
|
+
|
|
1759
|
+
{pendingEdits.size > 0 ? (
|
|
1760
|
+
<div className="fixed bottom-6 right-6 z-50 flex items-center gap-2 rounded-lg bg-white p-3 shadow-lg ring-1 ring-black/10 dark:bg-zinc-900 dark:ring-white/10">
|
|
1761
|
+
<Button
|
|
1762
|
+
type="button"
|
|
1763
|
+
variant="outline"
|
|
1764
|
+
size="sm"
|
|
1765
|
+
onClick={handleCancelPendingEdits}
|
|
1766
|
+
disabled={isSavingEdits}
|
|
1767
|
+
>
|
|
1768
|
+
Cancelar
|
|
1769
|
+
</Button>
|
|
1770
|
+
<Button
|
|
1771
|
+
type="button"
|
|
1772
|
+
size="sm"
|
|
1773
|
+
onClick={() => void handleSaveAllPendingEdits()}
|
|
1774
|
+
disabled={isSavingEdits}
|
|
1775
|
+
>
|
|
1776
|
+
{isSavingEdits ? 'Salvando...' : 'Salvar alterações'}
|
|
1777
|
+
</Button>
|
|
1778
|
+
</div>
|
|
1779
|
+
) : null}
|
|
1780
|
+
|
|
1781
|
+
<Dialog
|
|
1782
|
+
open={statementToDelete !== null}
|
|
1783
|
+
onOpenChange={(open) => {
|
|
1784
|
+
if (!open && !isDeletingStatement) setStatementToDelete(null);
|
|
1785
|
+
}}
|
|
1786
|
+
>
|
|
1787
|
+
<DialogContent className="sm:max-w-sm">
|
|
1788
|
+
<DialogHeader>
|
|
1789
|
+
<DialogTitle>Excluir movimentação</DialogTitle>
|
|
1790
|
+
<DialogDescription>
|
|
1791
|
+
Esta ação não pode ser desfeita. A movimentação será removida
|
|
1792
|
+
permanentemente do extrato.
|
|
1793
|
+
</DialogDescription>
|
|
1794
|
+
</DialogHeader>
|
|
1795
|
+
<DialogFooter className="mt-2">
|
|
1796
|
+
<Button
|
|
1797
|
+
variant="outline"
|
|
1798
|
+
onClick={() => setStatementToDelete(null)}
|
|
1799
|
+
disabled={isDeletingStatement}
|
|
1800
|
+
>
|
|
1801
|
+
Cancelar
|
|
1802
|
+
</Button>
|
|
1803
|
+
<Button
|
|
1804
|
+
variant="destructive"
|
|
1805
|
+
onClick={() => void confirmDeleteStatement()}
|
|
1806
|
+
disabled={isDeletingStatement}
|
|
1807
|
+
>
|
|
1808
|
+
{isDeletingStatement ? 'Excluindo...' : 'Excluir'}
|
|
1809
|
+
</Button>
|
|
1810
|
+
</DialogFooter>
|
|
1811
|
+
</DialogContent>
|
|
1812
|
+
</Dialog>
|
|
1142
1813
|
</Page>
|
|
1143
1814
|
);
|
|
1144
1815
|
}
|