@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,385 @@
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
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from '@/components/ui/dropdown-menu';
13
+ import { FilterBar } from '@/components/ui/filter-bar';
14
+ import { Input } from '@/components/ui/input';
15
+ import { Label } from '@/components/ui/label';
16
+ import { Money } from '@/components/ui/money';
17
+ import {
18
+ Select,
19
+ SelectContent,
20
+ SelectItem,
21
+ SelectTrigger,
22
+ SelectValue,
23
+ } from '@/components/ui/select';
24
+ import {
25
+ Sheet,
26
+ SheetContent,
27
+ SheetDescription,
28
+ SheetHeader,
29
+ SheetTitle,
30
+ SheetTrigger,
31
+ } from '@/components/ui/sheet';
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 { Textarea } from '@/components/ui/textarea';
42
+ import {
43
+ Download,
44
+ Edit,
45
+ Eye,
46
+ MoreHorizontal,
47
+ Paperclip,
48
+ Plus,
49
+ Send,
50
+ } from 'lucide-react';
51
+ import { useTranslations } from 'next-intl';
52
+ import Link from 'next/link';
53
+ import { useState } from 'react';
54
+ import { formatarData } from '../../_lib/formatters';
55
+ import { useFinanceData } from '../../_lib/use-finance-data';
56
+
57
+ function NovoTituloSheet({
58
+ pessoas,
59
+ categorias,
60
+ centrosCusto,
61
+ t,
62
+ }: {
63
+ pessoas: any[];
64
+ categorias: any[];
65
+ centrosCusto: any[];
66
+ t: ReturnType<typeof useTranslations>;
67
+ }) {
68
+ return (
69
+ <Sheet>
70
+ <SheetTrigger asChild>
71
+ <Button>
72
+ <Plus className="mr-2 h-4 w-4" />
73
+ {t('newTitle.action')}
74
+ </Button>
75
+ </SheetTrigger>
76
+ <SheetContent className="w-full sm:max-w-lg overflow-y-auto">
77
+ <SheetHeader>
78
+ <SheetTitle>{t('newTitle.title')}</SheetTitle>
79
+ <SheetDescription>{t('newTitle.description')}</SheetDescription>
80
+ </SheetHeader>
81
+ <form className="mt-6 space-y-4">
82
+ <div className="grid gap-4">
83
+ <div className="space-y-2">
84
+ <Label htmlFor="documento">{t('fields.document')}</Label>
85
+ <Input id="documento" placeholder="FAT-00000" />
86
+ </div>
87
+ <div className="space-y-2">
88
+ <Label htmlFor="cliente">{t('fields.client')}</Label>
89
+ <Select>
90
+ <SelectTrigger>
91
+ <SelectValue placeholder={t('common.select')} />
92
+ </SelectTrigger>
93
+ <SelectContent>
94
+ {pessoas
95
+ .filter((p) => p.tipo === 'cliente' || p.tipo === 'ambos')
96
+ .map((p) => (
97
+ <SelectItem key={p.id} value={p.id}>
98
+ {p.nome}
99
+ </SelectItem>
100
+ ))}
101
+ </SelectContent>
102
+ </Select>
103
+ </div>
104
+ <div className="grid grid-cols-2 gap-4">
105
+ <div className="space-y-2">
106
+ <Label htmlFor="competencia">{t('fields.competency')}</Label>
107
+ <Input id="competencia" type="month" />
108
+ </div>
109
+ <div className="space-y-2">
110
+ <Label htmlFor="vencimento">{t('fields.dueDate')}</Label>
111
+ <Input id="vencimento" type="date" />
112
+ </div>
113
+ </div>
114
+ <div className="space-y-2">
115
+ <Label htmlFor="valor">{t('fields.totalValue')}</Label>
116
+ <Input id="valor" type="number" placeholder="0,00" step="0.01" />
117
+ </div>
118
+ <div className="space-y-2">
119
+ <Label htmlFor="categoria">{t('fields.category')}</Label>
120
+ <Select>
121
+ <SelectTrigger>
122
+ <SelectValue placeholder={t('common.select')} />
123
+ </SelectTrigger>
124
+ <SelectContent>
125
+ {categorias
126
+ .filter((c) => c.natureza === 'receita')
127
+ .map((c) => (
128
+ <SelectItem key={c.id} value={c.id}>
129
+ {c.codigo} - {c.nome}
130
+ </SelectItem>
131
+ ))}
132
+ </SelectContent>
133
+ </Select>
134
+ </div>
135
+ <div className="space-y-2">
136
+ <Label htmlFor="centroCusto">{t('fields.costCenter')}</Label>
137
+ <Select>
138
+ <SelectTrigger>
139
+ <SelectValue placeholder={t('common.select')} />
140
+ </SelectTrigger>
141
+ <SelectContent>
142
+ {centrosCusto.map((c) => (
143
+ <SelectItem key={c.id} value={c.id}>
144
+ {c.codigo} - {c.nome}
145
+ </SelectItem>
146
+ ))}
147
+ </SelectContent>
148
+ </Select>
149
+ </div>
150
+ <div className="space-y-2">
151
+ <Label htmlFor="canal">{t('fields.channel')}</Label>
152
+ <Select>
153
+ <SelectTrigger>
154
+ <SelectValue placeholder={t('common.select')} />
155
+ </SelectTrigger>
156
+ <SelectContent>
157
+ <SelectItem value="boleto">{t('channels.boleto')}</SelectItem>
158
+ <SelectItem value="pix">PIX</SelectItem>
159
+ <SelectItem value="cartao">{t('channels.card')}</SelectItem>
160
+ <SelectItem value="transferencia">
161
+ {t('channels.transfer')}
162
+ </SelectItem>
163
+ </SelectContent>
164
+ </Select>
165
+ </div>
166
+ <div className="space-y-2">
167
+ <Label htmlFor="descricao">{t('fields.description')}</Label>
168
+ <Textarea
169
+ id="descricao"
170
+ placeholder={t('newTitle.descriptionPlaceholder')}
171
+ />
172
+ </div>
173
+ </div>
174
+ <div className="flex justify-end gap-2 pt-4">
175
+ <Button type="button" variant="outline">
176
+ {t('common.cancel')}
177
+ </Button>
178
+ <Button type="submit">{t('common.save')}</Button>
179
+ </div>
180
+ </form>
181
+ </SheetContent>
182
+ </Sheet>
183
+ );
184
+ }
185
+
186
+ export default function TitulosReceberPage() {
187
+ const t = useTranslations('finance.ReceivableInstallmentsPage');
188
+ const { data } = useFinanceData();
189
+ const { titulosReceber, pessoas, categorias, centrosCusto } = data;
190
+
191
+ const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
192
+
193
+ const [search, setSearch] = useState('');
194
+ const [statusFilter, setStatusFilter] = useState<string>('');
195
+
196
+ const canalBadge = {
197
+ boleto: {
198
+ label: t('channels.boleto'),
199
+ className: 'bg-blue-100 text-blue-700',
200
+ },
201
+ pix: { label: 'PIX', className: 'bg-green-100 text-green-700' },
202
+ cartao: {
203
+ label: t('channels.card'),
204
+ className: 'bg-purple-100 text-purple-700',
205
+ },
206
+ transferencia: {
207
+ label: t('channels.transfer'),
208
+ className: 'bg-orange-100 text-orange-700',
209
+ },
210
+ };
211
+
212
+ const filteredTitulos = titulosReceber.filter((titulo) => {
213
+ const matchesSearch =
214
+ titulo.documento.toLowerCase().includes(search.toLowerCase()) ||
215
+ getPessoaById(titulo.clienteId)
216
+ ?.nome.toLowerCase()
217
+ .includes(search.toLowerCase());
218
+
219
+ const matchesStatus = !statusFilter || titulo.status === statusFilter;
220
+
221
+ return matchesSearch && matchesStatus;
222
+ });
223
+
224
+ return (
225
+ <Page>
226
+ <PageHeader
227
+ title={t('header.title')}
228
+ description={t('header.description')}
229
+ breadcrumbs={[
230
+ { label: t('breadcrumbs.home'), href: '/' },
231
+ { label: t('breadcrumbs.finance'), href: '/finance' },
232
+ { label: t('breadcrumbs.current') },
233
+ ]}
234
+ actions={
235
+ <NovoTituloSheet
236
+ pessoas={pessoas}
237
+ categorias={categorias}
238
+ centrosCusto={centrosCusto}
239
+ t={t}
240
+ />
241
+ }
242
+ />
243
+
244
+ <FilterBar
245
+ searchPlaceholder={t('filters.searchPlaceholder')}
246
+ searchValue={search}
247
+ onSearchChange={setSearch}
248
+ filters={[
249
+ {
250
+ id: 'status',
251
+ label: t('filters.status'),
252
+ value: statusFilter,
253
+ onChange: setStatusFilter,
254
+ options: [
255
+ { value: 'all', label: t('statuses.all') },
256
+ { value: 'aberto', label: t('statuses.aberto') },
257
+ { value: 'parcial', label: t('statuses.parcial') },
258
+ { value: 'liquidado', label: t('statuses.liquidado') },
259
+ { value: 'vencido', label: t('statuses.vencido') },
260
+ { value: 'cancelado', label: t('statuses.cancelado') },
261
+ ],
262
+ },
263
+ ]}
264
+ activeFilters={statusFilter && statusFilter !== 'all' ? 1 : 0}
265
+ onClearFilters={() => setStatusFilter('')}
266
+ />
267
+
268
+ <div className="rounded-md border">
269
+ <Table>
270
+ <TableHeader>
271
+ <TableRow>
272
+ <TableHead>{t('table.headers.document')}</TableHead>
273
+ <TableHead>{t('table.headers.client')}</TableHead>
274
+ <TableHead>{t('table.headers.competency')}</TableHead>
275
+ <TableHead>{t('table.headers.dueDate')}</TableHead>
276
+ <TableHead className="text-right">
277
+ {t('table.headers.value')}
278
+ </TableHead>
279
+ <TableHead>{t('table.headers.channel')}</TableHead>
280
+ <TableHead>{t('table.headers.status')}</TableHead>
281
+ <TableHead className="w-[50px]" />
282
+ </TableRow>
283
+ </TableHeader>
284
+ <TableBody>
285
+ {filteredTitulos.map((titulo) => {
286
+ const cliente = getPessoaById(titulo.clienteId);
287
+ const canal =
288
+ canalBadge[titulo.canal as keyof typeof canalBadge] ||
289
+ canalBadge.transferencia;
290
+ const proximaParcela = titulo.parcelas.find(
291
+ (p: any) => p.status === 'aberto' || p.status === 'vencido'
292
+ );
293
+
294
+ return (
295
+ <TableRow key={titulo.id}>
296
+ <TableCell className="font-medium">
297
+ <Link
298
+ href={`/finance/accounts-receivable/installments/${titulo.id}`}
299
+ className="hover:underline"
300
+ >
301
+ {titulo.documento}
302
+ </Link>
303
+ {titulo.anexos.length > 0 && (
304
+ <Paperclip className="ml-1 inline h-3 w-3 text-muted-foreground" />
305
+ )}
306
+ </TableCell>
307
+ <TableCell>{cliente?.nome}</TableCell>
308
+ <TableCell>{titulo.competencia}</TableCell>
309
+ <TableCell>
310
+ {proximaParcela
311
+ ? formatarData(proximaParcela.vencimento)
312
+ : '-'}
313
+ </TableCell>
314
+ <TableCell className="text-right">
315
+ <Money value={titulo.valorTotal} />
316
+ </TableCell>
317
+ <TableCell>
318
+ <Badge className={canal.className} variant="outline">
319
+ {canal.label}
320
+ </Badge>
321
+ </TableCell>
322
+ <TableCell>
323
+ <StatusBadge status={titulo.status} />
324
+ </TableCell>
325
+ <TableCell>
326
+ <DropdownMenu>
327
+ <DropdownMenuTrigger asChild>
328
+ <Button variant="ghost" size="icon">
329
+ <MoreHorizontal className="h-4 w-4" />
330
+ <span className="sr-only">
331
+ {t('table.actions.srActions')}
332
+ </span>
333
+ </Button>
334
+ </DropdownMenuTrigger>
335
+ <DropdownMenuContent align="end">
336
+ <DropdownMenuItem asChild>
337
+ <Link
338
+ href={`/finance/accounts-receivable/installments/${titulo.id}`}
339
+ >
340
+ <Eye className="mr-2 h-4 w-4" />
341
+ {t('table.actions.viewDetails')}
342
+ </Link>
343
+ </DropdownMenuItem>
344
+ <DropdownMenuItem>
345
+ <Edit className="mr-2 h-4 w-4" />
346
+ {t('table.actions.edit')}
347
+ </DropdownMenuItem>
348
+ <DropdownMenuSeparator />
349
+ <DropdownMenuItem>
350
+ <Download className="mr-2 h-4 w-4" />
351
+ {t('table.actions.registerReceipt')}
352
+ </DropdownMenuItem>
353
+ <DropdownMenuItem>
354
+ <Send className="mr-2 h-4 w-4" />
355
+ {t('table.actions.sendCollection')}
356
+ </DropdownMenuItem>
357
+ </DropdownMenuContent>
358
+ </DropdownMenu>
359
+ </TableCell>
360
+ </TableRow>
361
+ );
362
+ })}
363
+ </TableBody>
364
+ </Table>
365
+ </div>
366
+
367
+ <div className="flex items-center justify-between">
368
+ <p className="text-sm text-muted-foreground">
369
+ {t('footer.showing', {
370
+ filtered: filteredTitulos.length,
371
+ total: titulosReceber.length,
372
+ })}
373
+ </p>
374
+ <div className="flex items-center gap-2">
375
+ <Button variant="outline" size="sm" disabled>
376
+ {t('footer.previous')}
377
+ </Button>
378
+ <Button variant="outline" size="sm" disabled>
379
+ {t('footer.next')}
380
+ </Button>
381
+ </div>
382
+ </div>
383
+ </Page>
384
+ );
385
+ }
@@ -0,0 +1,296 @@
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 { Input } from '@/components/ui/input';
14
+ import { Label } from '@/components/ui/label';
15
+ import { Money } from '@/components/ui/money';
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectItem,
20
+ SelectTrigger,
21
+ SelectValue,
22
+ } from '@/components/ui/select';
23
+ import {
24
+ Sheet,
25
+ SheetContent,
26
+ SheetDescription,
27
+ SheetHeader,
28
+ SheetTitle,
29
+ SheetTrigger,
30
+ } from '@/components/ui/sheet';
31
+ import {
32
+ Building2,
33
+ Eye,
34
+ Landmark,
35
+ PiggyBank,
36
+ Plus,
37
+ RefreshCw,
38
+ TrendingUp,
39
+ Upload,
40
+ Wallet,
41
+ } from 'lucide-react';
42
+ import { useTranslations } from 'next-intl';
43
+ import Link from 'next/link';
44
+ import { useFinanceData } from '../../_lib/use-finance-data';
45
+
46
+ function NovaContaSheet({ t }: { t: ReturnType<typeof useTranslations> }) {
47
+ return (
48
+ <Sheet>
49
+ <SheetTrigger asChild>
50
+ <Button>
51
+ <Plus className="mr-2 h-4 w-4" />
52
+ {t('newAccount.action')}
53
+ </Button>
54
+ </SheetTrigger>
55
+ <SheetContent className="w-full sm:max-w-lg">
56
+ <SheetHeader>
57
+ <SheetTitle>{t('newAccount.title')}</SheetTitle>
58
+ <SheetDescription>{t('newAccount.description')}</SheetDescription>
59
+ </SheetHeader>
60
+ <form className="mt-6 space-y-4">
61
+ <div className="grid gap-4">
62
+ <div className="space-y-2">
63
+ <Label htmlFor="banco">{t('fields.bank')}</Label>
64
+ <Input id="banco" placeholder={t('fields.bankPlaceholder')} />
65
+ </div>
66
+ <div className="grid grid-cols-2 gap-4">
67
+ <div className="space-y-2">
68
+ <Label htmlFor="agencia">{t('fields.branch')}</Label>
69
+ <Input id="agencia" placeholder="0000" />
70
+ </div>
71
+ <div className="space-y-2">
72
+ <Label htmlFor="conta">{t('fields.account')}</Label>
73
+ <Input id="conta" placeholder="00000-0" />
74
+ </div>
75
+ </div>
76
+ <div className="space-y-2">
77
+ <Label htmlFor="tipo">{t('fields.type')}</Label>
78
+ <Select>
79
+ <SelectTrigger>
80
+ <SelectValue placeholder={t('common.select')} />
81
+ </SelectTrigger>
82
+ <SelectContent>
83
+ <SelectItem value="corrente">
84
+ {t('types.corrente')}
85
+ </SelectItem>
86
+ <SelectItem value="poupanca">
87
+ {t('types.poupanca')}
88
+ </SelectItem>
89
+ <SelectItem value="investimento">
90
+ {t('types.investimento')}
91
+ </SelectItem>
92
+ <SelectItem value="caixa">{t('types.caixa')}</SelectItem>
93
+ </SelectContent>
94
+ </Select>
95
+ </div>
96
+ <div className="space-y-2">
97
+ <Label htmlFor="descricao">{t('fields.description')}</Label>
98
+ <Input
99
+ id="descricao"
100
+ placeholder={t('fields.descriptionPlaceholder')}
101
+ />
102
+ </div>
103
+ <div className="space-y-2">
104
+ <Label htmlFor="saldoInicial">{t('fields.initialBalance')}</Label>
105
+ <Input
106
+ id="saldoInicial"
107
+ type="number"
108
+ placeholder="0,00"
109
+ step="0.01"
110
+ />
111
+ </div>
112
+ </div>
113
+ <div className="flex justify-end gap-2 pt-4">
114
+ <Button type="button" variant="outline">
115
+ {t('common.cancel')}
116
+ </Button>
117
+ <Button type="submit">{t('common.save')}</Button>
118
+ </div>
119
+ </form>
120
+ </SheetContent>
121
+ </Sheet>
122
+ );
123
+ }
124
+
125
+ export default function ContasBancariasPage() {
126
+ const t = useTranslations('finance.BankAccountsPage');
127
+ const { data } = useFinanceData();
128
+ const { contasBancarias } = data;
129
+
130
+ const tipoConfig = {
131
+ corrente: { label: t('types.corrente'), icon: Building2 },
132
+ poupanca: { label: t('types.poupanca'), icon: PiggyBank },
133
+ investimento: { label: t('types.investimento'), icon: TrendingUp },
134
+ caixa: { label: t('types.caixa'), icon: Wallet },
135
+ };
136
+
137
+ const saldoTotal = contasBancarias
138
+ .filter((c) => c.ativo)
139
+ .reduce((acc, c) => acc + c.saldoAtual, 0);
140
+
141
+ const saldoConciliadoTotal = contasBancarias
142
+ .filter((c) => c.ativo)
143
+ .reduce((acc, c) => acc + c.saldoConciliado, 0);
144
+
145
+ return (
146
+ <Page>
147
+ <PageHeader
148
+ title={t('header.title')}
149
+ description={t('header.description')}
150
+ breadcrumbs={[
151
+ { label: t('breadcrumbs.home'), href: '/' },
152
+ { label: t('breadcrumbs.finance'), href: '/finance' },
153
+ { label: t('breadcrumbs.current') },
154
+ ]}
155
+ actions={<NovaContaSheet t={t} />}
156
+ />
157
+
158
+ <div className="grid gap-4 md:grid-cols-2">
159
+ <Card>
160
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
161
+ <CardTitle className="text-sm font-medium">
162
+ {t('cards.totalBalance')}
163
+ </CardTitle>
164
+ <Landmark className="h-4 w-4 text-muted-foreground" />
165
+ </CardHeader>
166
+ <CardContent>
167
+ <div className="text-2xl font-bold">
168
+ <Money value={saldoTotal} />
169
+ </div>
170
+ <p className="text-xs text-muted-foreground">
171
+ {t('cards.activeAccounts', {
172
+ count: contasBancarias.filter((c) => c.ativo).length,
173
+ })}
174
+ </p>
175
+ </CardContent>
176
+ </Card>
177
+ <Card>
178
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
179
+ <CardTitle className="text-sm font-medium">
180
+ {t('cards.reconciledBalance')}
181
+ </CardTitle>
182
+ <RefreshCw className="h-4 w-4 text-muted-foreground" />
183
+ </CardHeader>
184
+ <CardContent>
185
+ <div className="text-2xl font-bold">
186
+ <Money value={saldoConciliadoTotal} />
187
+ </div>
188
+ <p className="text-xs text-muted-foreground">
189
+ {t('cards.difference')}:{' '}
190
+ <Money value={saldoTotal - saldoConciliadoTotal} />
191
+ </p>
192
+ </CardContent>
193
+ </Card>
194
+ </div>
195
+
196
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
197
+ {contasBancarias.map((conta) => {
198
+ const tipo =
199
+ tipoConfig[conta.tipo as keyof typeof tipoConfig] ||
200
+ tipoConfig.corrente;
201
+ const TipoIcon = tipo.icon;
202
+ const diferenca = conta.saldoAtual - conta.saldoConciliado;
203
+
204
+ return (
205
+ <Card key={conta.id} className={!conta.ativo ? 'opacity-60' : ''}>
206
+ <CardHeader>
207
+ <div className="flex items-center justify-between">
208
+ <div className="flex items-center gap-2">
209
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-muted">
210
+ <TipoIcon className="h-5 w-5" />
211
+ </div>
212
+ <div>
213
+ <CardTitle className="text-base">{conta.banco}</CardTitle>
214
+ <CardDescription>
215
+ {conta.agencia !== '-'
216
+ ? t('accountCard.bankAccount', {
217
+ agency: conta.agencia,
218
+ account: conta.conta,
219
+ })
220
+ : conta.descricao}
221
+ </CardDescription>
222
+ </div>
223
+ </div>
224
+ {!conta.ativo && (
225
+ <Badge variant="outline" className="text-muted-foreground">
226
+ {t('status.inactive')}
227
+ </Badge>
228
+ )}
229
+ </div>
230
+ </CardHeader>
231
+ <CardContent>
232
+ <div className="space-y-3">
233
+ <div>
234
+ <p className="text-sm text-muted-foreground">
235
+ {t('accountCard.currentBalance')}
236
+ </p>
237
+ <p className="text-2xl font-bold">
238
+ <Money value={conta.saldoAtual} />
239
+ </p>
240
+ </div>
241
+ <div className="flex items-center justify-between text-sm">
242
+ <span className="text-muted-foreground">
243
+ {t('accountCard.reconciledBalance')}
244
+ </span>
245
+ <Money value={conta.saldoConciliado} />
246
+ </div>
247
+ {diferenca !== 0 && (
248
+ <div className="flex items-center justify-between text-sm">
249
+ <span className="text-muted-foreground">
250
+ {t('accountCard.difference')}
251
+ </span>
252
+ <span
253
+ className={
254
+ diferenca > 0 ? 'text-green-600' : 'text-red-600'
255
+ }
256
+ >
257
+ <Money value={diferenca} showSign />
258
+ </span>
259
+ </div>
260
+ )}
261
+ <div className="flex gap-2 pt-2">
262
+ <Button
263
+ variant="outline"
264
+ size="sm"
265
+ className="flex-1 bg-transparent"
266
+ asChild
267
+ >
268
+ <Link href="/finance/cash-and-banks/statements">
269
+ <Eye className="mr-2 h-4 w-4" />
270
+ {t('accountCard.statement')}
271
+ </Link>
272
+ </Button>
273
+ <Button
274
+ variant="outline"
275
+ size="sm"
276
+ className="flex-1 bg-transparent"
277
+ asChild
278
+ >
279
+ <Link href="/finance/cash-and-banks/bank-reconciliation">
280
+ <RefreshCw className="mr-2 h-4 w-4" />
281
+ {t('accountCard.reconcile')}
282
+ </Link>
283
+ </Button>
284
+ <Button variant="outline" size="sm">
285
+ <Upload className="h-4 w-4" />
286
+ </Button>
287
+ </div>
288
+ </div>
289
+ </CardContent>
290
+ </Card>
291
+ );
292
+ })}
293
+ </div>
294
+ </Page>
295
+ );
296
+ }