@hed-hog/finance 0.0.300 → 0.0.301

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 (39) hide show
  1. package/dist/finance.contract-activated.subscriber.d.ts +24 -0
  2. package/dist/finance.contract-activated.subscriber.d.ts.map +1 -0
  3. package/dist/finance.contract-activated.subscriber.js +519 -0
  4. package/dist/finance.contract-activated.subscriber.js.map +1 -0
  5. package/dist/finance.contract-activated.subscriber.spec.d.ts +2 -0
  6. package/dist/finance.contract-activated.subscriber.spec.d.ts.map +1 -0
  7. package/dist/finance.contract-activated.subscriber.spec.js +302 -0
  8. package/dist/finance.contract-activated.subscriber.spec.js.map +1 -0
  9. package/dist/finance.module.d.ts.map +1 -1
  10. package/dist/finance.module.js +6 -1
  11. package/dist/finance.module.js.map +1 -1
  12. package/hedhog/frontend/app/_components/finance-layout.tsx.ejs +108 -0
  13. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +91 -106
  14. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1306 -1145
  15. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +288 -268
  16. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +491 -351
  17. package/hedhog/frontend/app/administration/audit-logs/page.tsx.ejs +157 -173
  18. package/hedhog/frontend/app/administration/categories/page.tsx.ejs +44 -62
  19. package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +62 -80
  20. package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +151 -170
  21. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +332 -286
  22. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +204 -226
  23. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +122 -140
  24. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +32 -49
  25. package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +84 -108
  26. package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +53 -70
  27. package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +98 -95
  28. package/hedhog/frontend/app/reports/actual-vs-forecast/page.tsx.ejs +100 -125
  29. package/hedhog/frontend/app/reports/aging-default/page.tsx.ejs +77 -105
  30. package/hedhog/frontend/app/reports/cash-position/page.tsx.ejs +99 -134
  31. package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +147 -182
  32. package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +49 -61
  33. package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +49 -67
  34. package/hedhog/frontend/messages/en.json +176 -68
  35. package/hedhog/frontend/messages/pt.json +176 -68
  36. package/package.json +7 -6
  37. package/src/finance.contract-activated.subscriber.spec.ts +392 -0
  38. package/src/finance.contract-activated.subscriber.ts +780 -0
  39. package/src/finance.module.ts +6 -1
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { FinancePageSection } from '@/app/(app)/(libraries)/finance/_components/finance-layout';
3
4
  import { Page, PageHeader } from '@/components/entity-list';
4
5
  import { Badge } from '@/components/ui/badge';
5
6
  import {
@@ -9,6 +10,7 @@ import {
9
10
  CardHeader,
10
11
  CardTitle,
11
12
  } from '@/components/ui/card';
13
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
12
14
  import { Money } from '@/components/ui/money';
13
15
  import {
14
16
  Table,
@@ -48,6 +50,48 @@ export default function RecebiveisPage() {
48
50
  const totalLiquido = recebiveis.reduce((acc, r) => acc + r.liquido, 0);
49
51
  const taxasPercentual =
50
52
  totalBruto > 0 ? ((totalTaxas / totalBruto) * 100).toFixed(2) : '0.00';
53
+ const summaryCards = [
54
+ {
55
+ key: 'totalGross',
56
+ title: t('cards.totalGross'),
57
+ value: <Money value={totalBruto} />,
58
+ description: t('cards.receivablesCount', { count: recebiveis.length }),
59
+ icon: CalendarDays,
60
+ layout: 'compact' as const,
61
+ },
62
+ {
63
+ key: 'fees',
64
+ title: t('cards.fees'),
65
+ value: (
66
+ <span>
67
+ - <Money value={totalTaxas} />
68
+ </span>
69
+ ),
70
+ description: t('cards.percentOfGross', {
71
+ percent: taxasPercentual,
72
+ }),
73
+ layout: 'compact' as const,
74
+ valueClassName: 'text-red-600',
75
+ iconContainerClassName: 'bg-red-500/10 text-red-700',
76
+ accentClassName: 'from-red-500/20 via-rose-500/10 to-transparent',
77
+ },
78
+ {
79
+ key: 'totalNet',
80
+ title: t('cards.totalNet'),
81
+ value: <Money value={totalLiquido} />,
82
+ layout: 'compact' as const,
83
+ valueClassName: 'text-green-600',
84
+ iconContainerClassName: 'bg-green-500/10 text-green-700',
85
+ accentClassName: 'from-green-500/20 via-emerald-500/10 to-transparent',
86
+ },
87
+ {
88
+ key: 'confirmed',
89
+ title: t('cards.confirmed'),
90
+ value: recebiveis.filter((r) => r.status === 'confirmado').length,
91
+ description: t('cards.ofTotal', { total: recebiveis.length }),
92
+ layout: 'compact' as const,
93
+ },
94
+ ];
51
95
 
52
96
  return (
53
97
  <Page>
@@ -61,68 +105,7 @@ export default function RecebiveisPage() {
61
105
  ]}
62
106
  />
63
107
 
64
- <div className="grid gap-4 md:grid-cols-4">
65
- <Card>
66
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
67
- <CardTitle className="text-sm font-medium">
68
- {t('cards.totalGross')}
69
- </CardTitle>
70
- <CalendarDays className="h-4 w-4 text-muted-foreground" />
71
- </CardHeader>
72
- <CardContent>
73
- <div className="text-2xl font-bold">
74
- <Money value={totalBruto} />
75
- </div>
76
- <p className="text-xs text-muted-foreground">
77
- {t('cards.receivablesCount', { count: recebiveis.length })}
78
- </p>
79
- </CardContent>
80
- </Card>
81
- <Card>
82
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
83
- <CardTitle className="text-sm font-medium">
84
- {t('cards.fees')}
85
- </CardTitle>
86
- </CardHeader>
87
- <CardContent>
88
- <div className="text-2xl font-bold text-red-600">
89
- - <Money value={totalTaxas} />
90
- </div>
91
- <p className="text-xs text-muted-foreground">
92
- {t('cards.percentOfGross', {
93
- percent: taxasPercentual,
94
- })}
95
- </p>
96
- </CardContent>
97
- </Card>
98
- <Card>
99
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
100
- <CardTitle className="text-sm font-medium">
101
- {t('cards.totalNet')}
102
- </CardTitle>
103
- </CardHeader>
104
- <CardContent>
105
- <div className="text-2xl font-bold text-green-600">
106
- <Money value={totalLiquido} />
107
- </div>
108
- </CardContent>
109
- </Card>
110
- <Card>
111
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
112
- <CardTitle className="text-sm font-medium">
113
- {t('cards.confirmed')}
114
- </CardTitle>
115
- </CardHeader>
116
- <CardContent>
117
- <div className="text-2xl font-bold">
118
- {recebiveis.filter((r) => r.status === 'confirmado').length}
119
- </div>
120
- <p className="text-xs text-muted-foreground">
121
- {t('cards.ofTotal', { total: recebiveis.length })}
122
- </p>
123
- </CardContent>
124
- </Card>
125
- </div>
108
+ <KpiCardsGrid items={summaryCards} columns={4} />
126
109
 
127
110
  <div className="grid gap-6 lg:grid-cols-3">
128
111
  {adquirentes.map((adquirente) => {
@@ -164,12 +147,12 @@ export default function RecebiveisPage() {
164
147
  })}
165
148
  </div>
166
149
 
167
- <Card>
168
- <CardHeader>
169
- <CardTitle>{t('table.title')}</CardTitle>
170
- <CardDescription>{t('table.description')}</CardDescription>
171
- </CardHeader>
172
- <CardContent>
150
+ <FinancePageSection
151
+ title={t('table.title')}
152
+ description={t('table.description')}
153
+ contentClassName="p-4 sm:p-5"
154
+ >
155
+ <div className="overflow-x-auto">
173
156
  <Table>
174
157
  <TableHeader>
175
158
  <TableRow>
@@ -220,8 +203,8 @@ export default function RecebiveisPage() {
220
203
  })}
221
204
  </TableBody>
222
205
  </Table>
223
- </CardContent>
224
- </Card>
206
+ </div>
207
+ </FinancePageSection>
225
208
  </Page>
226
209
  );
227
210
  }
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { FinancePageSection } from '@/app/(app)/(libraries)/finance/_components/finance-layout';
3
4
  import { Page, PageHeader } from '@/components/entity-list';
4
5
  import { Badge } from '@/components/ui/badge';
5
6
  import { Button } from '@/components/ui/button';
@@ -84,109 +85,111 @@ export default function CenariosPage() {
84
85
  ]}
85
86
  />
86
87
 
87
- <div className="grid gap-4 md:grid-cols-3">
88
- {cenarios.map((cenario) => {
89
- const isAtivo = cenario.id === cenarioAtivo;
90
- const Icon =
91
- cenario.nome === 'Otimista'
92
- ? TrendingUp
93
- : cenario.nome === 'Pessimista'
94
- ? TrendingDown
95
- : Target;
88
+ <FinancePageSection contentClassName="p-4 sm:p-5">
89
+ <div className="grid gap-4 md:grid-cols-3">
90
+ {cenarios.map((cenario) => {
91
+ const isAtivo = cenario.id === cenarioAtivo;
92
+ const Icon =
93
+ cenario.nome === 'Otimista'
94
+ ? TrendingUp
95
+ : cenario.nome === 'Pessimista'
96
+ ? TrendingDown
97
+ : Target;
96
98
 
97
- return (
98
- <Card
99
- key={cenario.id}
100
- className={`cursor-pointer transition-all ${
101
- isAtivo
102
- ? 'border-primary ring-2 ring-primary/20'
103
- : 'hover:border-muted-foreground/50'
104
- }`}
105
- onClick={() => {
106
- setCenarioAtivo(cenario.id);
107
- setPremissas({
108
- atrasoMedio: cenario.atrasoMedio,
109
- taxaInadimplencia: cenario.taxaInadimplencia,
110
- crescimentoReceita: cenario.crescimentoReceita,
111
- });
112
- }}
113
- >
114
- <CardHeader>
115
- <div className="flex items-center justify-between">
116
- <div className="flex items-center gap-3">
117
- <div
118
- className={`flex h-10 w-10 items-center justify-center rounded-lg ${
119
- cenario.nome === 'Otimista'
120
- ? 'bg-green-100'
121
- : cenario.nome === 'Pessimista'
122
- ? 'bg-red-100'
123
- : 'bg-blue-100'
124
- }`}
125
- >
126
- <Icon
127
- className={`h-5 w-5 ${
99
+ return (
100
+ <Card
101
+ key={cenario.id}
102
+ className={`cursor-pointer transition-all ${
103
+ isAtivo
104
+ ? 'border-primary ring-2 ring-primary/20'
105
+ : 'hover:border-muted-foreground/50'
106
+ }`}
107
+ onClick={() => {
108
+ setCenarioAtivo(cenario.id);
109
+ setPremissas({
110
+ atrasoMedio: cenario.atrasoMedio,
111
+ taxaInadimplencia: cenario.taxaInadimplencia,
112
+ crescimentoReceita: cenario.crescimentoReceita,
113
+ });
114
+ }}
115
+ >
116
+ <CardHeader>
117
+ <div className="flex items-center justify-between">
118
+ <div className="flex items-center gap-3">
119
+ <div
120
+ className={`flex h-10 w-10 items-center justify-center rounded-lg ${
128
121
  cenario.nome === 'Otimista'
129
- ? 'text-green-600'
122
+ ? 'bg-green-100'
130
123
  : cenario.nome === 'Pessimista'
131
- ? 'text-red-600'
132
- : 'text-blue-600'
124
+ ? 'bg-red-100'
125
+ : 'bg-blue-100'
133
126
  }`}
134
- />
127
+ >
128
+ <Icon
129
+ className={`h-5 w-5 ${
130
+ cenario.nome === 'Otimista'
131
+ ? 'text-green-600'
132
+ : cenario.nome === 'Pessimista'
133
+ ? 'text-red-600'
134
+ : 'text-blue-600'
135
+ }`}
136
+ />
137
+ </div>
138
+ <div>
139
+ <CardTitle className="text-base">
140
+ {cenario.nome}
141
+ </CardTitle>
142
+ <CardDescription>{cenario.descricao}</CardDescription>
143
+ </div>
135
144
  </div>
145
+ {isAtivo && (
146
+ <Badge className="bg-primary text-primary-foreground">
147
+ <Check className="mr-1 h-3 w-3" />
148
+ {t('cards.active')}
149
+ </Badge>
150
+ )}
151
+ </div>
152
+ </CardHeader>
153
+ <CardContent>
154
+ <div className="grid grid-cols-3 gap-4 text-center">
136
155
  <div>
137
- <CardTitle className="text-base">
138
- {cenario.nome}
139
- </CardTitle>
140
- <CardDescription>{cenario.descricao}</CardDescription>
156
+ <p className="text-xs text-muted-foreground">
157
+ {t('cards.delay')}
158
+ </p>
159
+ <p className="font-semibold">
160
+ {t('cards.days', { value: cenario.atrasoMedio })}
161
+ </p>
162
+ </div>
163
+ <div>
164
+ <p className="text-xs text-muted-foreground">
165
+ {t('cards.defaultRate')}
166
+ </p>
167
+ <p className="font-semibold">
168
+ {cenario.taxaInadimplencia}%
169
+ </p>
170
+ </div>
171
+ <div>
172
+ <p className="text-xs text-muted-foreground">
173
+ {t('cards.growth')}
174
+ </p>
175
+ <p
176
+ className={`font-semibold ${
177
+ cenario.crescimentoReceita >= 0
178
+ ? 'text-green-600'
179
+ : 'text-red-600'
180
+ }`}
181
+ >
182
+ {cenario.crescimentoReceita > 0 && '+'}
183
+ {cenario.crescimentoReceita}%
184
+ </p>
141
185
  </div>
142
186
  </div>
143
- {isAtivo && (
144
- <Badge className="bg-primary text-primary-foreground">
145
- <Check className="mr-1 h-3 w-3" />
146
- {t('cards.active')}
147
- </Badge>
148
- )}
149
- </div>
150
- </CardHeader>
151
- <CardContent>
152
- <div className="grid grid-cols-3 gap-4 text-center">
153
- <div>
154
- <p className="text-xs text-muted-foreground">
155
- {t('cards.delay')}
156
- </p>
157
- <p className="font-semibold">
158
- {t('cards.days', { value: cenario.atrasoMedio })}
159
- </p>
160
- </div>
161
- <div>
162
- <p className="text-xs text-muted-foreground">
163
- {t('cards.defaultRate')}
164
- </p>
165
- <p className="font-semibold">
166
- {cenario.taxaInadimplencia}%
167
- </p>
168
- </div>
169
- <div>
170
- <p className="text-xs text-muted-foreground">
171
- {t('cards.growth')}
172
- </p>
173
- <p
174
- className={`font-semibold ${
175
- cenario.crescimentoReceita >= 0
176
- ? 'text-green-600'
177
- : 'text-red-600'
178
- }`}
179
- >
180
- {cenario.crescimentoReceita > 0 && '+'}
181
- {cenario.crescimentoReceita}%
182
- </p>
183
- </div>
184
- </div>
185
- </CardContent>
186
- </Card>
187
- );
188
- })}
189
- </div>
187
+ </CardContent>
188
+ </Card>
189
+ );
190
+ })}
191
+ </div>
192
+ </FinancePageSection>
190
193
 
191
194
  <div className="grid gap-6 lg:grid-cols-2">
192
195
  <Card>
@@ -1,15 +1,10 @@
1
1
  'use client';
2
2
 
3
+ import { FinancePageSection } from '@/app/(app)/(libraries)/finance/_components/finance-layout';
3
4
  import { Page, PageHeader } from '@/components/entity-list';
4
5
  import { Badge } from '@/components/ui/badge';
5
6
  import { Button } from '@/components/ui/button';
6
- import {
7
- Card,
8
- CardContent,
9
- CardDescription,
10
- CardHeader,
11
- CardTitle,
12
- } from '@/components/ui/card';
7
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
13
8
  import { Money } from '@/components/ui/money';
14
9
  import {
15
10
  Select,
@@ -104,6 +99,47 @@ export default function ActualVsForecastPage() {
104
99
  acuracia,
105
100
  };
106
101
  }, [comparisonData]);
102
+ const summaryCards = [
103
+ {
104
+ key: 'forecast',
105
+ title: t('cards.totalForecast'),
106
+ value: <Money value={totals.previsto} />,
107
+ icon: Target,
108
+ layout: 'compact' as const,
109
+ },
110
+ {
111
+ key: 'actual',
112
+ title: t('cards.totalActual'),
113
+ value: <Money value={totals.realizado} />,
114
+ icon: TrendingUp,
115
+ layout: 'compact' as const,
116
+ iconContainerClassName: 'bg-blue-500/10 text-blue-700',
117
+ accentClassName: 'from-blue-500/20 via-sky-500/10 to-transparent',
118
+ },
119
+ {
120
+ key: 'deviation',
121
+ title: t('cards.totalDeviation'),
122
+ value: <Money value={totals.desvio} showSign />,
123
+ icon: totals.desvio >= 0 ? TrendingUp : TrendingDown,
124
+ layout: 'compact' as const,
125
+ valueClassName: totals.desvio >= 0 ? 'text-green-600' : 'text-red-600',
126
+ iconContainerClassName:
127
+ totals.desvio >= 0
128
+ ? 'bg-green-500/10 text-green-700'
129
+ : 'bg-red-500/10 text-red-700',
130
+ accentClassName:
131
+ totals.desvio >= 0
132
+ ? 'from-green-500/20 via-emerald-500/10 to-transparent'
133
+ : 'from-red-500/20 via-rose-500/10 to-transparent',
134
+ },
135
+ {
136
+ key: 'accuracy',
137
+ title: t('cards.accuracy'),
138
+ value: `${totals.acuracia.toFixed(1)}%`,
139
+ icon: Target,
140
+ layout: 'compact' as const,
141
+ },
142
+ ];
107
143
 
108
144
  return (
109
145
  <Page>
@@ -129,7 +165,7 @@ export default function ActualVsForecastPage() {
129
165
  onValueChange={setHorizonte}
130
166
  disabled={isFetching}
131
167
  >
132
- <SelectTrigger className="w-[180px]">
168
+ <SelectTrigger className="w-45">
133
169
  <SelectValue placeholder={t('filters.horizon')} />
134
170
  </SelectTrigger>
135
171
  <SelectContent>
@@ -145,7 +181,7 @@ export default function ActualVsForecastPage() {
145
181
  onValueChange={(value) => setCenario(value as Scenario)}
146
182
  disabled={isFetching}
147
183
  >
148
- <SelectTrigger className="w-[180px]">
184
+ <SelectTrigger className="w-45">
149
185
  <SelectValue placeholder={t('filters.scenario')} />
150
186
  </SelectTrigger>
151
187
  <SelectContent>
@@ -166,122 +202,61 @@ export default function ActualVsForecastPage() {
166
202
  ) : null}
167
203
  </div>
168
204
 
169
- <div className="grid gap-4 md:grid-cols-4">
170
- <Card>
171
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
172
- <CardTitle className="text-sm font-medium">
173
- {t('cards.totalForecast')}
174
- </CardTitle>
175
- <Target className="h-4 w-4 text-muted-foreground" />
176
- </CardHeader>
177
- <CardContent>
178
- <div className="text-2xl font-bold">
179
- <Money value={totals.previsto} />
180
- </div>
181
- </CardContent>
182
- </Card>
183
- <Card>
184
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
185
- <CardTitle className="text-sm font-medium">
186
- {t('cards.totalActual')}
187
- </CardTitle>
188
- <TrendingUp className="h-4 w-4 text-muted-foreground" />
189
- </CardHeader>
190
- <CardContent>
191
- <div className="text-2xl font-bold">
192
- <Money value={totals.realizado} />
193
- </div>
194
- </CardContent>
195
- </Card>
196
- <Card>
197
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
198
- <CardTitle className="text-sm font-medium">
199
- {t('cards.totalDeviation')}
200
- </CardTitle>
201
- {totals.desvio >= 0 ? (
202
- <TrendingUp className="h-4 w-4 text-green-600" />
203
- ) : (
204
- <TrendingDown className="h-4 w-4 text-red-600" />
205
- )}
206
- </CardHeader>
207
- <CardContent>
208
- <div
209
- className={`text-2xl font-bold ${totals.desvio >= 0 ? 'text-green-600' : 'text-red-600'}`}
210
- >
211
- <Money value={totals.desvio} showSign />
212
- </div>
213
- </CardContent>
214
- </Card>
215
- <Card>
216
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
217
- <CardTitle className="text-sm font-medium">
218
- {t('cards.accuracy')}
219
- </CardTitle>
220
- <Target className="h-4 w-4 text-muted-foreground" />
221
- </CardHeader>
222
- <CardContent>
223
- <div className="text-2xl font-bold">
224
- {totals.acuracia.toFixed(1)}%
225
- </div>
226
- </CardContent>
227
- </Card>
228
- </div>
205
+ <KpiCardsGrid items={summaryCards} columns={4} />
229
206
 
230
- <Card>
231
- <CardHeader>
232
- <CardTitle>{t('chart.title')}</CardTitle>
233
- <CardDescription>{t('chart.description')}</CardDescription>
234
- </CardHeader>
235
- <CardContent>
236
- <ResponsiveContainer width="100%" height={340}>
237
- <LineChart data={comparisonData}>
238
- <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
239
- <XAxis dataKey="dataLabel" tick={{ fontSize: 12 }} />
240
- <YAxis
241
- tick={{ fontSize: 12 }}
242
- tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
243
- />
244
- <Tooltip
245
- formatter={(value: number) =>
246
- new Intl.NumberFormat('pt-BR', {
247
- style: 'currency',
248
- currency: 'BRL',
249
- }).format(value)
250
- }
251
- contentStyle={{
252
- backgroundColor: 'hsl(var(--background))',
253
- border: '1px solid hsl(var(--border))',
254
- borderRadius: '8px',
255
- }}
256
- />
257
- <Legend />
258
- <Line
259
- type="monotone"
260
- dataKey="previsto"
261
- name={t('chart.forecastLabel')}
262
- stroke="hsl(var(--primary))"
263
- strokeWidth={2}
264
- dot={false}
265
- />
266
- <Line
267
- type="monotone"
268
- dataKey="realizado"
269
- name={t('chart.actualLabel')}
270
- stroke="hsl(var(--chart-2))"
271
- strokeWidth={2}
272
- dot={false}
273
- />
274
- </LineChart>
275
- </ResponsiveContainer>
276
- </CardContent>
277
- </Card>
207
+ <FinancePageSection
208
+ title={t('chart.title')}
209
+ description={t('chart.description')}
210
+ contentClassName="p-4 sm:p-5"
211
+ >
212
+ <ResponsiveContainer width="100%" height={340}>
213
+ <LineChart data={comparisonData}>
214
+ <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
215
+ <XAxis dataKey="dataLabel" tick={{ fontSize: 12 }} />
216
+ <YAxis
217
+ tick={{ fontSize: 12 }}
218
+ tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
219
+ />
220
+ <Tooltip
221
+ formatter={(value: number) =>
222
+ new Intl.NumberFormat('pt-BR', {
223
+ style: 'currency',
224
+ currency: 'BRL',
225
+ }).format(value)
226
+ }
227
+ contentStyle={{
228
+ backgroundColor: 'hsl(var(--background))',
229
+ border: '1px solid hsl(var(--border))',
230
+ borderRadius: '8px',
231
+ }}
232
+ />
233
+ <Legend />
234
+ <Line
235
+ type="monotone"
236
+ dataKey="previsto"
237
+ name={t('chart.forecastLabel')}
238
+ stroke="hsl(var(--primary))"
239
+ strokeWidth={2}
240
+ dot={false}
241
+ />
242
+ <Line
243
+ type="monotone"
244
+ dataKey="realizado"
245
+ name={t('chart.actualLabel')}
246
+ stroke="hsl(var(--chart-2))"
247
+ strokeWidth={2}
248
+ dot={false}
249
+ />
250
+ </LineChart>
251
+ </ResponsiveContainer>
252
+ </FinancePageSection>
278
253
 
279
- <Card>
280
- <CardHeader>
281
- <CardTitle>{t('table.title')}</CardTitle>
282
- <CardDescription>{t('table.description')}</CardDescription>
283
- </CardHeader>
284
- <CardContent>
254
+ <FinancePageSection
255
+ title={t('table.title')}
256
+ description={t('table.description')}
257
+ contentClassName="p-4 sm:p-5"
258
+ >
259
+ <div className="overflow-x-auto">
285
260
  <Table>
286
261
  <TableHeader>
287
262
  <TableRow>
@@ -354,8 +329,8 @@ export default function ActualVsForecastPage() {
354
329
  )}
355
330
  </TableBody>
356
331
  </Table>
357
- </CardContent>
358
- </Card>
332
+ </div>
333
+ </FinancePageSection>
359
334
  </Page>
360
335
  );
361
336
  }