@hed-hog/finance 0.0.223 → 0.0.225

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/dto/create-financial-title.dto.d.ts +19 -0
  2. package/dist/dto/create-financial-title.dto.d.ts.map +1 -0
  3. package/dist/dto/create-financial-title.dto.js +128 -0
  4. package/dist/dto/create-financial-title.dto.js.map +1 -0
  5. package/dist/finance.controller.d.ts +276 -0
  6. package/dist/finance.controller.d.ts.map +1 -0
  7. package/dist/finance.controller.js +110 -0
  8. package/dist/finance.controller.js.map +1 -0
  9. package/dist/finance.module.d.ts.map +1 -1
  10. package/dist/finance.module.js +8 -3
  11. package/dist/finance.module.js.map +1 -1
  12. package/dist/finance.service.d.ts +295 -0
  13. package/dist/finance.service.d.ts.map +1 -0
  14. package/dist/finance.service.js +416 -0
  15. package/dist/finance.service.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -0
  19. package/dist/index.js.map +1 -1
  20. package/hedhog/data/menu.yaml +72 -25
  21. package/hedhog/data/route.yaml +55 -1
  22. package/hedhog/frontend/app/_lib/formatters.ts.ejs +20 -0
  23. package/hedhog/frontend/app/_lib/use-finance-data.ts.ejs +87 -0
  24. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +290 -0
  25. package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +410 -0
  26. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +388 -0
  27. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +423 -0
  28. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +411 -0
  29. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +385 -0
  30. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +296 -0
  31. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +427 -0
  32. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +273 -0
  33. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +253 -0
  34. package/hedhog/frontend/app/page.tsx.ejs +338 -17
  35. package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +298 -0
  36. package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +225 -0
  37. package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +338 -0
  38. package/hedhog/frontend/messages/en.json +776 -0
  39. package/hedhog/frontend/messages/pt.json +776 -0
  40. package/hedhog/query/constraints.sql +169 -0
  41. package/package.json +3 -3
  42. package/src/dto/create-financial-title.dto.ts +142 -0
  43. package/src/finance.controller.ts +89 -0
  44. package/src/finance.module.ts +8 -3
  45. package/src/finance.service.ts +529 -0
  46. package/src/index.ts +4 -0
@@ -0,0 +1,427 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Badge } from '@/components/ui/badge';
5
+ import { Button } from '@/components/ui/button';
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from '@/components/ui/card';
13
+ import { Checkbox } from '@/components/ui/checkbox';
14
+ import {
15
+ Dialog,
16
+ DialogContent,
17
+ DialogDescription,
18
+ DialogFooter,
19
+ DialogHeader,
20
+ DialogTitle,
21
+ DialogTrigger,
22
+ } from '@/components/ui/dialog';
23
+ import { Input } from '@/components/ui/input';
24
+ import { Label } from '@/components/ui/label';
25
+ import { Money } from '@/components/ui/money';
26
+ import { Progress } from '@/components/ui/progress';
27
+ import {
28
+ Select,
29
+ SelectContent,
30
+ SelectItem,
31
+ SelectTrigger,
32
+ SelectValue,
33
+ } from '@/components/ui/select';
34
+ import { StatusBadge } from '@/components/ui/status-badge';
35
+ import { Check, DollarSign, Link2, RefreshCw } from 'lucide-react';
36
+ import { useTranslations } from 'next-intl';
37
+ import { useState } from 'react';
38
+ import { formatarData, formatarMoeda } from '../../_lib/formatters';
39
+ import { useFinanceData } from '../../_lib/use-finance-data';
40
+
41
+ function CriarAjusteDialog({ t }: { t: ReturnType<typeof useTranslations> }) {
42
+ return (
43
+ <Dialog>
44
+ <DialogTrigger asChild>
45
+ <Button variant="outline" size="sm">
46
+ <DollarSign className="mr-2 h-4 w-4" />
47
+ {t('adjustment.action')}
48
+ </Button>
49
+ </DialogTrigger>
50
+ <DialogContent>
51
+ <DialogHeader>
52
+ <DialogTitle>{t('adjustment.title')}</DialogTitle>
53
+ <DialogDescription>{t('adjustment.description')}</DialogDescription>
54
+ </DialogHeader>
55
+ <div className="space-y-4">
56
+ <div className="space-y-2">
57
+ <Label htmlFor="tipo">{t('adjustment.type')}</Label>
58
+ <Select>
59
+ <SelectTrigger>
60
+ <SelectValue placeholder={t('common.select')} />
61
+ </SelectTrigger>
62
+ <SelectContent>
63
+ <SelectItem value="tarifa">
64
+ {t('adjustment.types.bankFee')}
65
+ </SelectItem>
66
+ <SelectItem value="juros">
67
+ {t('adjustment.types.interest')}
68
+ </SelectItem>
69
+ <SelectItem value="diferenca">
70
+ {t('adjustment.types.valueDifference')}
71
+ </SelectItem>
72
+ <SelectItem value="outro">
73
+ {t('adjustment.types.other')}
74
+ </SelectItem>
75
+ </SelectContent>
76
+ </Select>
77
+ </div>
78
+ <div className="space-y-2">
79
+ <Label htmlFor="valor">{t('adjustment.value')}</Label>
80
+ <Input id="valor" type="number" placeholder="0,00" />
81
+ </div>
82
+ <div className="space-y-2">
83
+ <Label htmlFor="descricao">{t('adjustment.details')}</Label>
84
+ <Input
85
+ id="descricao"
86
+ placeholder={t('adjustment.detailsPlaceholder')}
87
+ />
88
+ </div>
89
+ </div>
90
+ <DialogFooter>
91
+ <Button variant="outline">{t('common.cancel')}</Button>
92
+ <Button>{t('adjustment.submit')}</Button>
93
+ </DialogFooter>
94
+ </DialogContent>
95
+ </Dialog>
96
+ );
97
+ }
98
+
99
+ export default function ConciliacaoPage() {
100
+ const t = useTranslations('finance.BankReconciliationPage');
101
+ const { data } = useFinanceData();
102
+ const { extratos, titulosPagar, titulosReceber, contasBancarias, pessoas } =
103
+ data;
104
+
105
+ const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
106
+
107
+ const extratosPendentes = extratos.filter(
108
+ (e) => e.statusConciliacao === 'pendente'
109
+ );
110
+
111
+ const titulosAbertos = [
112
+ ...titulosPagar.flatMap((t: any) =>
113
+ t.parcelas
114
+ .filter((p: any) => p.status === 'aberto' || p.status === 'vencido')
115
+ .map((p: any) => ({
116
+ id: `pagar-${t.id}-${p.id}`,
117
+ tipo: 'pagar' as const,
118
+ documento: t.documento,
119
+ pessoa: getPessoaById(t.fornecedorId)?.nome || '',
120
+ vencimento: p.vencimento,
121
+ valor: p.valor,
122
+ }))
123
+ ),
124
+ ...titulosReceber.flatMap((t: any) =>
125
+ t.parcelas
126
+ .filter((p: any) => p.status === 'aberto' || p.status === 'vencido')
127
+ .map((p: any) => ({
128
+ id: `receber-${t.id}-${p.id}`,
129
+ tipo: 'receber' as const,
130
+ documento: t.documento,
131
+ pessoa: getPessoaById(t.clienteId)?.nome || '',
132
+ vencimento: p.vencimento,
133
+ valor: p.valor,
134
+ }))
135
+ ),
136
+ ];
137
+
138
+ const [contaFilter, setContaFilter] = useState<string>('1');
139
+ const [selectedExtrato, setSelectedExtrato] = useState<string | null>(null);
140
+ const [selectedTitulo, setSelectedTitulo] = useState<string | null>(null);
141
+
142
+ const extratosFiltered = extratosPendentes.filter(
143
+ (e) => e.contaBancariaId === contaFilter
144
+ );
145
+
146
+ const totalConciliado = extratos.filter(
147
+ (e) =>
148
+ e.contaBancariaId === contaFilter && e.statusConciliacao === 'conciliado'
149
+ ).length;
150
+ const totalExtratoConta = extratos.filter(
151
+ (e) => e.contaBancariaId === contaFilter
152
+ ).length;
153
+ const percentualConciliado =
154
+ totalExtratoConta > 0
155
+ ? Math.round((totalConciliado / totalExtratoConta) * 100)
156
+ : 0;
157
+
158
+ return (
159
+ <Page>
160
+ <PageHeader
161
+ title={t('header.title')}
162
+ description={t('header.description')}
163
+ breadcrumbs={[
164
+ { label: t('breadcrumbs.home'), href: '/' },
165
+ { label: t('breadcrumbs.finance'), href: '/finance' },
166
+ { label: t('breadcrumbs.current') },
167
+ ]}
168
+ />
169
+
170
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
171
+ <Select value={contaFilter} onValueChange={setContaFilter}>
172
+ <SelectTrigger className="w-[280px]">
173
+ <SelectValue placeholder={t('filters.selectAccount')} />
174
+ </SelectTrigger>
175
+ <SelectContent>
176
+ {contasBancarias.map((conta) => (
177
+ <SelectItem key={conta.id} value={conta.id}>
178
+ {conta.banco} - {conta.descricao}
179
+ </SelectItem>
180
+ ))}
181
+ </SelectContent>
182
+ </Select>
183
+ <div className="flex gap-2">
184
+ <CriarAjusteDialog t={t} />
185
+ <Button disabled={!selectedExtrato || !selectedTitulo}>
186
+ <Link2 className="mr-2 h-4 w-4" />
187
+ {t('actions.reconcileSelected')}
188
+ </Button>
189
+ </div>
190
+ </div>
191
+
192
+ <div className="grid gap-4 md:grid-cols-4">
193
+ <Card>
194
+ <CardHeader className="pb-2">
195
+ <CardTitle className="text-sm font-medium">
196
+ {t('cards.reconciled')}
197
+ </CardTitle>
198
+ </CardHeader>
199
+ <CardContent>
200
+ <div className="text-2xl font-bold">{percentualConciliado}%</div>
201
+ <Progress value={percentualConciliado} className="mt-2" />
202
+ </CardContent>
203
+ </Card>
204
+ <Card>
205
+ <CardHeader className="pb-2">
206
+ <CardTitle className="text-sm font-medium">
207
+ {t('cards.pending')}
208
+ </CardTitle>
209
+ </CardHeader>
210
+ <CardContent>
211
+ <div className="text-2xl font-bold">{extratosFiltered.length}</div>
212
+ <p className="text-xs text-muted-foreground">
213
+ {t('cards.transactions')}
214
+ </p>
215
+ </CardContent>
216
+ </Card>
217
+ <Card>
218
+ <CardHeader className="pb-2">
219
+ <CardTitle className="text-sm font-medium">
220
+ {t('cards.discrepancies')}
221
+ </CardTitle>
222
+ </CardHeader>
223
+ <CardContent>
224
+ <div className="text-2xl font-bold text-orange-600">0</div>
225
+ <p className="text-xs text-muted-foreground">
226
+ {t('cards.toResolve')}
227
+ </p>
228
+ </CardContent>
229
+ </Card>
230
+ <Card>
231
+ <CardHeader className="pb-2">
232
+ <CardTitle className="text-sm font-medium">
233
+ {t('cards.difference')}
234
+ </CardTitle>
235
+ </CardHeader>
236
+ <CardContent>
237
+ <div className="text-2xl font-bold">
238
+ <Money value={2230.5} />
239
+ </div>
240
+ <p className="text-xs text-muted-foreground">
241
+ {t('cards.notReconciled')}
242
+ </p>
243
+ </CardContent>
244
+ </Card>
245
+ </div>
246
+
247
+ <div className="grid gap-6 lg:grid-cols-2">
248
+ <Card>
249
+ <CardHeader>
250
+ <CardTitle className="flex items-center gap-2">
251
+ <RefreshCw className="h-4 w-4" />
252
+ {t('statement.title')}
253
+ </CardTitle>
254
+ <CardDescription>{t('statement.description')}</CardDescription>
255
+ </CardHeader>
256
+ <CardContent>
257
+ <div className="space-y-2">
258
+ {extratosFiltered.length > 0 ? (
259
+ extratosFiltered.map((extrato) => (
260
+ <div
261
+ key={extrato.id}
262
+ className={`flex items-center gap-3 rounded-lg border p-3 cursor-pointer transition-colors ${
263
+ selectedExtrato === extrato.id
264
+ ? 'border-primary bg-primary/5'
265
+ : 'hover:bg-muted/50'
266
+ }`}
267
+ onClick={() =>
268
+ setSelectedExtrato(
269
+ selectedExtrato === extrato.id ? null : extrato.id
270
+ )
271
+ }
272
+ >
273
+ <Checkbox
274
+ checked={selectedExtrato === extrato.id}
275
+ onCheckedChange={() =>
276
+ setSelectedExtrato(
277
+ selectedExtrato === extrato.id ? null : extrato.id
278
+ )
279
+ }
280
+ />
281
+ <div className="flex-1">
282
+ <div className="flex items-center justify-between">
283
+ <span className="text-sm font-medium">
284
+ {extrato.descricao}
285
+ </span>
286
+ <span
287
+ className={`font-semibold ${
288
+ extrato.tipo === 'entrada'
289
+ ? 'text-green-600'
290
+ : 'text-red-600'
291
+ }`}
292
+ >
293
+ {extrato.tipo === 'saida' && '-'}
294
+ {formatarMoeda(extrato.valor)}
295
+ </span>
296
+ </div>
297
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
298
+ <span>{formatarData(extrato.data)}</span>
299
+ <StatusBadge
300
+ status={extrato.statusConciliacao}
301
+ type="conciliacao"
302
+ />
303
+ </div>
304
+ </div>
305
+ </div>
306
+ ))
307
+ ) : (
308
+ <div className="flex flex-col items-center justify-center py-8 text-center">
309
+ <Check className="h-12 w-12 text-green-500" />
310
+ <p className="mt-2 font-medium">
311
+ {t('statement.emptyTitle')}
312
+ </p>
313
+ <p className="text-sm text-muted-foreground">
314
+ {t('statement.emptyDescription')}
315
+ </p>
316
+ </div>
317
+ )}
318
+ </div>
319
+ </CardContent>
320
+ </Card>
321
+
322
+ <Card>
323
+ <CardHeader>
324
+ <CardTitle className="flex items-center gap-2">
325
+ <DollarSign className="h-4 w-4" />
326
+ {t('openTitles.title')}
327
+ </CardTitle>
328
+ <CardDescription>{t('openTitles.description')}</CardDescription>
329
+ </CardHeader>
330
+ <CardContent>
331
+ <div className="space-y-2 max-h-[400px] overflow-y-auto">
332
+ {titulosAbertos.map((titulo) => (
333
+ <div
334
+ key={titulo.id}
335
+ className={`flex items-center gap-3 rounded-lg border p-3 cursor-pointer transition-colors ${
336
+ selectedTitulo === titulo.id
337
+ ? 'border-primary bg-primary/5'
338
+ : 'hover:bg-muted/50'
339
+ }`}
340
+ onClick={() =>
341
+ setSelectedTitulo(
342
+ selectedTitulo === titulo.id ? null : titulo.id
343
+ )
344
+ }
345
+ >
346
+ <Checkbox
347
+ checked={selectedTitulo === titulo.id}
348
+ onCheckedChange={() =>
349
+ setSelectedTitulo(
350
+ selectedTitulo === titulo.id ? null : titulo.id
351
+ )
352
+ }
353
+ />
354
+ <div className="flex-1">
355
+ <div className="flex items-center justify-between">
356
+ <div className="flex items-center gap-2">
357
+ <span className="text-sm font-medium">
358
+ {titulo.documento}
359
+ </span>
360
+ <Badge
361
+ variant="outline"
362
+ className={
363
+ titulo.tipo === 'pagar'
364
+ ? 'border-red-200 text-red-700'
365
+ : 'border-green-200 text-green-700'
366
+ }
367
+ >
368
+ {titulo.tipo === 'pagar'
369
+ ? t('openTitles.pay')
370
+ : t('openTitles.receive')}
371
+ </Badge>
372
+ </div>
373
+ <span className="font-semibold">
374
+ {formatarMoeda(titulo.valor)}
375
+ </span>
376
+ </div>
377
+ <div className="text-xs text-muted-foreground">
378
+ {t('openTitles.personDue', {
379
+ person: titulo.pessoa,
380
+ dueDate: formatarData(titulo.vencimento),
381
+ })}
382
+ </div>
383
+ </div>
384
+ </div>
385
+ ))}
386
+ </div>
387
+ </CardContent>
388
+ </Card>
389
+ </div>
390
+
391
+ {selectedExtrato && selectedTitulo && (
392
+ <Card className="border-primary">
393
+ <CardHeader>
394
+ <CardTitle className="flex items-center gap-2 text-primary">
395
+ <Link2 className="h-4 w-4" />
396
+ {t('ready.title')}
397
+ </CardTitle>
398
+ </CardHeader>
399
+ <CardContent>
400
+ <div className="flex items-center justify-between">
401
+ <div>
402
+ <p className="text-sm text-muted-foreground">
403
+ {t('ready.description')}
404
+ </p>
405
+ </div>
406
+ <div className="flex gap-2">
407
+ <Button
408
+ variant="outline"
409
+ onClick={() => {
410
+ setSelectedExtrato(null);
411
+ setSelectedTitulo(null);
412
+ }}
413
+ >
414
+ {t('common.cancel')}
415
+ </Button>
416
+ <Button>
417
+ <Check className="mr-2 h-4 w-4" />
418
+ {t('actions.reconcile')}
419
+ </Button>
420
+ </div>
421
+ </div>
422
+ </CardContent>
423
+ </Card>
424
+ )}
425
+ </Page>
426
+ );
427
+ }
@@ -0,0 +1,273 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '@/components/ui/card';
12
+ import {
13
+ Dialog,
14
+ DialogContent,
15
+ DialogDescription,
16
+ DialogFooter,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ DialogTrigger,
20
+ } from '@/components/ui/dialog';
21
+ import { FilterBar } from '@/components/ui/filter-bar';
22
+ import { Input } from '@/components/ui/input';
23
+ import { Label } from '@/components/ui/label';
24
+ import { Money } from '@/components/ui/money';
25
+ import {
26
+ Select,
27
+ SelectContent,
28
+ SelectItem,
29
+ SelectTrigger,
30
+ SelectValue,
31
+ } from '@/components/ui/select';
32
+ import { StatusBadge } from '@/components/ui/status-badge';
33
+ import {
34
+ Table,
35
+ TableBody,
36
+ TableCell,
37
+ TableHead,
38
+ TableHeader,
39
+ TableRow,
40
+ } from '@/components/ui/table';
41
+ import { ArrowDownRight, ArrowUpRight, Download, Upload } from 'lucide-react';
42
+ import { useTranslations } from 'next-intl';
43
+ import { useState } from 'react';
44
+ import { formatarData } from '../../_lib/formatters';
45
+ import { useFinanceData } from '../../_lib/use-finance-data';
46
+
47
+ function ImportarExtratoDialog({
48
+ contasBancarias,
49
+ t,
50
+ }: {
51
+ contasBancarias: any[];
52
+ t: ReturnType<typeof useTranslations>;
53
+ }) {
54
+ return (
55
+ <Dialog>
56
+ <DialogTrigger asChild>
57
+ <Button>
58
+ <Upload className="mr-2 h-4 w-4" />
59
+ {t('importDialog.action')}
60
+ </Button>
61
+ </DialogTrigger>
62
+ <DialogContent>
63
+ <DialogHeader>
64
+ <DialogTitle>{t('importDialog.title')}</DialogTitle>
65
+ <DialogDescription>{t('importDialog.description')}</DialogDescription>
66
+ </DialogHeader>
67
+ <div className="space-y-4">
68
+ <div className="space-y-2">
69
+ <Label htmlFor="conta">{t('importDialog.bankAccount')}</Label>
70
+ <Select>
71
+ <SelectTrigger>
72
+ <SelectValue placeholder={t('importDialog.selectAccount')} />
73
+ </SelectTrigger>
74
+ <SelectContent>
75
+ {contasBancarias.map((conta: any) => (
76
+ <SelectItem key={conta.id} value={conta.id}>
77
+ {conta.banco} - {conta.descricao}
78
+ </SelectItem>
79
+ ))}
80
+ </SelectContent>
81
+ </Select>
82
+ </div>
83
+ <div className="space-y-2">
84
+ <Label htmlFor="arquivo">{t('importDialog.file')}</Label>
85
+ <Input id="arquivo" type="file" accept=".ofx,.csv" />
86
+ <p className="text-xs text-muted-foreground">
87
+ {t('importDialog.acceptedFormats')}
88
+ </p>
89
+ </div>
90
+ </div>
91
+ <DialogFooter>
92
+ <Button variant="outline">{t('common.cancel')}</Button>
93
+ <Button>{t('importDialog.submit')}</Button>
94
+ </DialogFooter>
95
+ </DialogContent>
96
+ </Dialog>
97
+ );
98
+ }
99
+
100
+ export default function ExtratosPage() {
101
+ const t = useTranslations('finance.StatementsPage');
102
+ const { data } = useFinanceData();
103
+ const { extratos, contasBancarias } = data;
104
+
105
+ const [contaFilter, setContaFilter] = useState<string>('1');
106
+ const [search, setSearch] = useState('');
107
+
108
+ const filteredExtratos = extratos.filter((extrato) => {
109
+ const matchesConta = extrato.contaBancariaId === contaFilter;
110
+ const matchesSearch = extrato.descricao
111
+ .toLowerCase()
112
+ .includes(search.toLowerCase());
113
+ return matchesConta && matchesSearch;
114
+ });
115
+
116
+ const conta = contasBancarias.find((item) => item.id === contaFilter);
117
+ const totalEntradas = filteredExtratos
118
+ .filter((e) => e.tipo === 'entrada')
119
+ .reduce((acc, e) => acc + e.valor, 0);
120
+ const totalSaidas = filteredExtratos
121
+ .filter((e) => e.tipo === 'saida')
122
+ .reduce((acc, e) => acc + e.valor, 0);
123
+
124
+ return (
125
+ <Page>
126
+ <PageHeader
127
+ title={t('header.title')}
128
+ description={t('header.description')}
129
+ breadcrumbs={[
130
+ { label: t('breadcrumbs.home'), href: '/' },
131
+ { label: t('breadcrumbs.finance'), href: '/finance' },
132
+ { label: t('breadcrumbs.current') },
133
+ ]}
134
+ actions={
135
+ <div className="flex gap-2">
136
+ <Button variant="outline">
137
+ <Download className="mr-2 h-4 w-4" />
138
+ {t('actions.export')}
139
+ </Button>
140
+ <ImportarExtratoDialog contasBancarias={contasBancarias} t={t} />
141
+ </div>
142
+ }
143
+ />
144
+
145
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center">
146
+ <Select value={contaFilter} onValueChange={setContaFilter}>
147
+ <SelectTrigger className="w-[280px]">
148
+ <SelectValue placeholder={t('filters.selectAccount')} />
149
+ </SelectTrigger>
150
+ <SelectContent>
151
+ {contasBancarias.map((conta) => (
152
+ <SelectItem key={conta.id} value={conta.id}>
153
+ {conta.banco} - {conta.descricao}
154
+ </SelectItem>
155
+ ))}
156
+ </SelectContent>
157
+ </Select>
158
+ <div className="flex-1">
159
+ <FilterBar
160
+ searchPlaceholder={t('filters.searchPlaceholder')}
161
+ searchValue={search}
162
+ onSearchChange={setSearch}
163
+ />
164
+ </div>
165
+ </div>
166
+
167
+ <div className="grid gap-4 md:grid-cols-3">
168
+ <Card>
169
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
170
+ <CardTitle className="text-sm font-medium">
171
+ {t('cards.inflows')}
172
+ </CardTitle>
173
+ <ArrowUpRight className="h-4 w-4 text-green-500" />
174
+ </CardHeader>
175
+ <CardContent>
176
+ <div className="text-2xl font-bold text-green-600">
177
+ <Money value={totalEntradas} />
178
+ </div>
179
+ </CardContent>
180
+ </Card>
181
+ <Card>
182
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
183
+ <CardTitle className="text-sm font-medium">
184
+ {t('cards.outflows')}
185
+ </CardTitle>
186
+ <ArrowDownRight className="h-4 w-4 text-red-500" />
187
+ </CardHeader>
188
+ <CardContent>
189
+ <div className="text-2xl font-bold text-red-600">
190
+ <Money value={totalSaidas} />
191
+ </div>
192
+ </CardContent>
193
+ </Card>
194
+ <Card>
195
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
196
+ <CardTitle className="text-sm font-medium">
197
+ {t('cards.accountBalance')}
198
+ </CardTitle>
199
+ </CardHeader>
200
+ <CardContent>
201
+ <div className="text-2xl font-bold">
202
+ <Money value={conta?.saldoAtual || 0} />
203
+ </div>
204
+ <p className="text-xs text-muted-foreground">{conta?.descricao}</p>
205
+ </CardContent>
206
+ </Card>
207
+ </div>
208
+
209
+ <Card>
210
+ <CardHeader>
211
+ <CardTitle>{t('table.title')}</CardTitle>
212
+ <CardDescription>
213
+ {t('table.foundTransactions', { count: filteredExtratos.length })}
214
+ </CardDescription>
215
+ </CardHeader>
216
+ <CardContent>
217
+ <Table>
218
+ <TableHeader>
219
+ <TableRow>
220
+ <TableHead>{t('table.headers.date')}</TableHead>
221
+ <TableHead>{t('table.headers.description')}</TableHead>
222
+ <TableHead className="text-right">
223
+ {t('table.headers.value')}
224
+ </TableHead>
225
+ <TableHead>{t('table.headers.type')}</TableHead>
226
+ <TableHead>{t('table.headers.reconciliation')}</TableHead>
227
+ </TableRow>
228
+ </TableHeader>
229
+ <TableBody>
230
+ {filteredExtratos.map((extrato) => (
231
+ <TableRow key={extrato.id}>
232
+ <TableCell>{formatarData(extrato.data)}</TableCell>
233
+ <TableCell>{extrato.descricao}</TableCell>
234
+ <TableCell className="text-right">
235
+ <span
236
+ className={
237
+ extrato.tipo === 'entrada'
238
+ ? 'text-green-600'
239
+ : 'text-red-600'
240
+ }
241
+ >
242
+ {extrato.tipo === 'saida' && '-'}
243
+ <Money value={extrato.valor} />
244
+ </span>
245
+ </TableCell>
246
+ <TableCell>
247
+ {extrato.tipo === 'entrada' ? (
248
+ <span className="flex items-center gap-1 text-green-600">
249
+ <ArrowUpRight className="h-4 w-4" />
250
+ {t('types.inflow')}
251
+ </span>
252
+ ) : (
253
+ <span className="flex items-center gap-1 text-red-600">
254
+ <ArrowDownRight className="h-4 w-4" />
255
+ {t('types.outflow')}
256
+ </span>
257
+ )}
258
+ </TableCell>
259
+ <TableCell>
260
+ <StatusBadge
261
+ status={extrato.statusConciliacao}
262
+ type="conciliacao"
263
+ />
264
+ </TableCell>
265
+ </TableRow>
266
+ ))}
267
+ </TableBody>
268
+ </Table>
269
+ </CardContent>
270
+ </Card>
271
+ </Page>
272
+ );
273
+ }