@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.
- package/dist/finance.contract-activated.subscriber.d.ts +24 -0
- package/dist/finance.contract-activated.subscriber.d.ts.map +1 -0
- package/dist/finance.contract-activated.subscriber.js +519 -0
- package/dist/finance.contract-activated.subscriber.js.map +1 -0
- package/dist/finance.contract-activated.subscriber.spec.d.ts +2 -0
- package/dist/finance.contract-activated.subscriber.spec.d.ts.map +1 -0
- package/dist/finance.contract-activated.subscriber.spec.js +302 -0
- package/dist/finance.contract-activated.subscriber.spec.js.map +1 -0
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +6 -1
- package/dist/finance.module.js.map +1 -1
- package/hedhog/frontend/app/_components/finance-layout.tsx.ejs +108 -0
- package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +91 -106
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1306 -1145
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +288 -268
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +491 -351
- package/hedhog/frontend/app/administration/audit-logs/page.tsx.ejs +157 -173
- package/hedhog/frontend/app/administration/categories/page.tsx.ejs +44 -62
- package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +62 -80
- package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +151 -170
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +332 -286
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +204 -226
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +122 -140
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +32 -49
- package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +84 -108
- package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +53 -70
- package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +98 -95
- package/hedhog/frontend/app/reports/actual-vs-forecast/page.tsx.ejs +100 -125
- package/hedhog/frontend/app/reports/aging-default/page.tsx.ejs +77 -105
- package/hedhog/frontend/app/reports/cash-position/page.tsx.ejs +99 -134
- package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +147 -182
- package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +49 -61
- package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +49 -67
- package/hedhog/frontend/messages/en.json +176 -68
- package/hedhog/frontend/messages/pt.json +176 -68
- package/package.json +7 -6
- package/src/finance.contract-activated.subscriber.spec.ts +392 -0
- package/src/finance.contract-activated.subscriber.ts +780 -0
- 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
|
-
<
|
|
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
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<
|
|
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
|
-
</
|
|
224
|
-
</
|
|
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
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
? '
|
|
122
|
+
? 'bg-green-100'
|
|
130
123
|
: cenario.nome === 'Pessimista'
|
|
131
|
-
? '
|
|
132
|
-
: '
|
|
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
|
-
<
|
|
138
|
-
{
|
|
139
|
-
</
|
|
140
|
-
<
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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-
|
|
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-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
<
|
|
236
|
-
<
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
<
|
|
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
|
-
</
|
|
358
|
-
</
|
|
332
|
+
</div>
|
|
333
|
+
</FinancePageSection>
|
|
359
334
|
</Page>
|
|
360
335
|
);
|
|
361
336
|
}
|