@hed-hog/finance 0.0.279 → 0.0.285
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dto/finance-report-query.dto.d.ts +16 -0
- package/dist/dto/finance-report-query.dto.d.ts.map +1 -0
- package/dist/dto/finance-report-query.dto.js +59 -0
- package/dist/dto/finance-report-query.dto.js.map +1 -0
- package/dist/finance-reports.controller.d.ts +71 -0
- package/dist/finance-reports.controller.d.ts.map +1 -0
- package/dist/finance-reports.controller.js +61 -0
- package/dist/finance-reports.controller.js.map +1 -0
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +2 -0
- package/dist/finance.module.js.map +1 -1
- package/dist/finance.service.d.ts +93 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +456 -0
- package/dist/finance.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/hedhog/data/route.yaml +27 -0
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +158 -125
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +102 -88
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +113 -89
- package/hedhog/frontend/app/reports/_lib/use-finance-reports.ts.ejs +233 -0
- package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +96 -78
- package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +247 -130
- package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +250 -135
- package/hedhog/frontend/messages/en.json +33 -2
- package/hedhog/frontend/messages/pt.json +33 -2
- package/package.json +6 -6
- package/src/dto/finance-report-query.dto.ts +49 -0
- package/src/finance-reports.controller.ts +28 -0
- package/src/finance.module.ts +2 -0
- package/src/finance.service.ts +645 -10
- package/src/index.ts +1 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Page, PageHeader } from '@/components/entity-list';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
4
5
|
import {
|
|
5
6
|
Card,
|
|
6
7
|
CardContent,
|
|
@@ -9,7 +10,6 @@ import {
|
|
|
9
10
|
CardTitle,
|
|
10
11
|
} from '@/components/ui/card';
|
|
11
12
|
import { Input } from '@/components/ui/input';
|
|
12
|
-
import { Label } from '@/components/ui/label';
|
|
13
13
|
import { Money } from '@/components/ui/money';
|
|
14
14
|
import {
|
|
15
15
|
Select,
|
|
@@ -28,11 +28,13 @@ import {
|
|
|
28
28
|
} from '@/components/ui/table';
|
|
29
29
|
import { Building2, CircleDollarSign, TriangleAlert } from 'lucide-react';
|
|
30
30
|
import { useTranslations } from 'next-intl';
|
|
31
|
-
import {
|
|
31
|
+
import { type FormEvent, useState } from 'react';
|
|
32
32
|
import {
|
|
33
33
|
Bar,
|
|
34
34
|
BarChart,
|
|
35
|
+
CartesianGrid,
|
|
35
36
|
Cell,
|
|
37
|
+
Legend,
|
|
36
38
|
Pie,
|
|
37
39
|
PieChart,
|
|
38
40
|
ResponsiveContainer,
|
|
@@ -41,47 +43,93 @@ import {
|
|
|
41
43
|
YAxis,
|
|
42
44
|
} from 'recharts';
|
|
43
45
|
import {
|
|
44
|
-
aggregateOperationalExpenses,
|
|
45
46
|
getDefaultDateRange,
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
type GroupBy,
|
|
48
|
+
useTopOperationalExpensesReport,
|
|
49
|
+
} from '../_lib/use-finance-reports';
|
|
48
50
|
|
|
49
|
-
const
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'#
|
|
56
|
-
'#
|
|
57
|
-
'#
|
|
51
|
+
const rankingColors = [
|
|
52
|
+
'#2563EB',
|
|
53
|
+
'#DC2626',
|
|
54
|
+
'#059669',
|
|
55
|
+
'#D97706',
|
|
56
|
+
'#7C3AED',
|
|
57
|
+
'#0F766E',
|
|
58
|
+
'#BE123C',
|
|
59
|
+
'#1D4ED8',
|
|
58
60
|
];
|
|
59
61
|
|
|
62
|
+
const currencyFormatter = new Intl.NumberFormat('pt-BR', {
|
|
63
|
+
style: 'currency',
|
|
64
|
+
currency: 'BRL',
|
|
65
|
+
});
|
|
66
|
+
|
|
60
67
|
export default function TopOperationalExpensesReportPage() {
|
|
61
68
|
const t = useTranslations('finance.TopOperationalExpensesReportPage');
|
|
62
69
|
const defaults = getDefaultDateRange();
|
|
70
|
+
const [filters, setFilters] = useState({
|
|
71
|
+
from: defaults.from,
|
|
72
|
+
to: defaults.to,
|
|
73
|
+
groupBy: 'year' as GroupBy,
|
|
74
|
+
search: '',
|
|
75
|
+
});
|
|
76
|
+
const [appliedFilters, setAppliedFilters] = useState(filters);
|
|
77
|
+
const { data: viewData, isFetching } = useTopOperationalExpensesReport({
|
|
78
|
+
from: appliedFilters.from,
|
|
79
|
+
to: appliedFilters.to,
|
|
80
|
+
groupBy: appliedFilters.groupBy,
|
|
81
|
+
search: appliedFilters.search,
|
|
82
|
+
topN: 20,
|
|
83
|
+
});
|
|
63
84
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
const handleSubmitFilters = (event: FormEvent<HTMLFormElement>) => {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
setAppliedFilters(filters);
|
|
88
|
+
};
|
|
67
89
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
const renderSolidTooltip = ({
|
|
91
|
+
active,
|
|
92
|
+
payload,
|
|
93
|
+
label,
|
|
94
|
+
}: {
|
|
95
|
+
active?: boolean;
|
|
96
|
+
payload?: Array<{
|
|
97
|
+
name?: string;
|
|
98
|
+
value?: number | string;
|
|
99
|
+
color?: string;
|
|
100
|
+
dataKey?: string;
|
|
101
|
+
}>;
|
|
102
|
+
label?: string | number;
|
|
103
|
+
}) => {
|
|
104
|
+
if (!active || !payload || payload.length === 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
78
107
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
return (
|
|
109
|
+
<div className="min-w-52 rounded-md border bg-popover px-3 py-2 shadow-xl">
|
|
110
|
+
<p className="mb-1 text-sm font-semibold text-popover-foreground">
|
|
111
|
+
{String(label ?? payload[0]?.name ?? '')}
|
|
112
|
+
</p>
|
|
113
|
+
{payload.map((row, index) => (
|
|
114
|
+
<div
|
|
115
|
+
key={`${row.dataKey ?? row.name ?? 'item'}-${index}`}
|
|
116
|
+
className="flex items-center justify-between gap-3 text-sm"
|
|
117
|
+
>
|
|
118
|
+
<div className="flex items-center gap-2 text-popover-foreground">
|
|
119
|
+
<span
|
|
120
|
+
className="inline-block h-2.5 w-2.5 rounded-full"
|
|
121
|
+
style={{ backgroundColor: row.color || 'hsl(var(--chart-1))' }}
|
|
122
|
+
/>
|
|
123
|
+
<span>{row.name ?? row.dataKey ?? 'Valor'}</span>
|
|
124
|
+
</div>
|
|
125
|
+
<span className="font-semibold text-popover-foreground">
|
|
126
|
+
{currencyFormatter.format(Number(row.value || 0))}
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
85
133
|
|
|
86
134
|
return (
|
|
87
135
|
<Page>
|
|
@@ -95,166 +143,231 @@ export default function TopOperationalExpensesReportPage() {
|
|
|
95
143
|
]}
|
|
96
144
|
/>
|
|
97
145
|
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
<form
|
|
147
|
+
onSubmit={handleSubmitFilters}
|
|
148
|
+
className="flex w-full flex-col gap-3 lg:flex-row"
|
|
149
|
+
>
|
|
150
|
+
<Input
|
|
151
|
+
type="search"
|
|
152
|
+
value={filters.search}
|
|
153
|
+
onChange={(event) =>
|
|
154
|
+
setFilters((current) => ({
|
|
155
|
+
...current,
|
|
156
|
+
search: event.target.value,
|
|
157
|
+
}))
|
|
158
|
+
}
|
|
159
|
+
placeholder={t('filters.searchPlaceholder')}
|
|
160
|
+
className="w-full lg:flex-[2.4]"
|
|
161
|
+
aria-label={t('filters.searchAria')}
|
|
162
|
+
/>
|
|
163
|
+
<Input
|
|
164
|
+
id="from"
|
|
165
|
+
type="date"
|
|
166
|
+
value={filters.from}
|
|
167
|
+
onChange={(event) =>
|
|
168
|
+
setFilters((current) => ({
|
|
169
|
+
...current,
|
|
170
|
+
from: event.target.value,
|
|
171
|
+
}))
|
|
172
|
+
}
|
|
173
|
+
max={filters.to}
|
|
174
|
+
className="w-full lg:w-[170px]"
|
|
175
|
+
aria-label={t('filters.fromAria')}
|
|
176
|
+
placeholder={t('filters.fromPlaceholder')}
|
|
177
|
+
/>
|
|
178
|
+
<Input
|
|
179
|
+
id="to"
|
|
180
|
+
type="date"
|
|
181
|
+
value={filters.to}
|
|
182
|
+
onChange={(event) =>
|
|
183
|
+
setFilters((current) => ({
|
|
184
|
+
...current,
|
|
185
|
+
to: event.target.value,
|
|
186
|
+
}))
|
|
187
|
+
}
|
|
188
|
+
min={filters.from}
|
|
189
|
+
className="w-full lg:w-[170px]"
|
|
190
|
+
aria-label={t('filters.toAria')}
|
|
191
|
+
placeholder={t('filters.toPlaceholder')}
|
|
192
|
+
/>
|
|
193
|
+
<Select
|
|
194
|
+
value={filters.groupBy}
|
|
195
|
+
onValueChange={(value) =>
|
|
196
|
+
setFilters((current) => ({
|
|
197
|
+
...current,
|
|
198
|
+
groupBy: value as GroupBy,
|
|
199
|
+
}))
|
|
200
|
+
}
|
|
201
|
+
>
|
|
202
|
+
<SelectTrigger
|
|
203
|
+
className="w-full lg:w-[150px]"
|
|
204
|
+
aria-label={t('filters.groupByAria')}
|
|
205
|
+
>
|
|
206
|
+
<SelectValue placeholder={t('filters.groupByPlaceholder')} />
|
|
207
|
+
</SelectTrigger>
|
|
208
|
+
<SelectContent>
|
|
209
|
+
<SelectItem value="day">{t('groupBy.day')}</SelectItem>
|
|
210
|
+
<SelectItem value="week">{t('groupBy.week')}</SelectItem>
|
|
211
|
+
<SelectItem value="month">{t('groupBy.month')}</SelectItem>
|
|
212
|
+
<SelectItem value="year">{t('groupBy.year')}</SelectItem>
|
|
213
|
+
</SelectContent>
|
|
214
|
+
</Select>
|
|
215
|
+
<Button
|
|
216
|
+
type="submit"
|
|
217
|
+
className="w-full lg:w-auto"
|
|
218
|
+
disabled={isFetching}
|
|
219
|
+
>
|
|
220
|
+
{t('filters.submit')}
|
|
221
|
+
</Button>
|
|
222
|
+
</form>
|
|
143
223
|
|
|
144
|
-
<div className="grid gap-
|
|
224
|
+
<div className="grid gap-3 md:grid-cols-3">
|
|
145
225
|
<Card>
|
|
146
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-
|
|
147
|
-
<CardTitle className="text-
|
|
226
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
|
|
227
|
+
<CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
148
228
|
{t('cards.total')}
|
|
149
229
|
</CardTitle>
|
|
150
|
-
<
|
|
230
|
+
<div className="rounded-full bg-orange-500/10 p-1.5">
|
|
231
|
+
<CircleDollarSign className="h-4 w-4 text-orange-600" />
|
|
232
|
+
</div>
|
|
151
233
|
</CardHeader>
|
|
152
|
-
<CardContent>
|
|
153
|
-
<div className="text-
|
|
154
|
-
<Money value={
|
|
234
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
235
|
+
<div className="text-xl font-bold">
|
|
236
|
+
<Money value={viewData.total} />
|
|
155
237
|
</div>
|
|
156
238
|
</CardContent>
|
|
157
239
|
</Card>
|
|
158
240
|
|
|
159
241
|
<Card>
|
|
160
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-
|
|
161
|
-
<CardTitle className="text-
|
|
242
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
|
|
243
|
+
<CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
162
244
|
{t('cards.highestExpense')}
|
|
163
245
|
</CardTitle>
|
|
164
|
-
<
|
|
246
|
+
<div className="rounded-full bg-amber-500/10 p-1.5">
|
|
247
|
+
<TriangleAlert className="h-4 w-4 text-amber-500" />
|
|
248
|
+
</div>
|
|
165
249
|
</CardHeader>
|
|
166
|
-
<CardContent>
|
|
167
|
-
<div className="text-
|
|
168
|
-
{highest?.category || '-'}
|
|
250
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
251
|
+
<div className="text-sm font-medium">
|
|
252
|
+
{viewData.highest?.category || '-'}
|
|
169
253
|
</div>
|
|
170
|
-
<div className="text-muted-foreground
|
|
171
|
-
{highest ?
|
|
254
|
+
<div className="text-xs text-muted-foreground">
|
|
255
|
+
{viewData.highest ? (
|
|
256
|
+
<Money value={viewData.highest.value} />
|
|
257
|
+
) : (
|
|
258
|
+
'-'
|
|
259
|
+
)}
|
|
172
260
|
</div>
|
|
173
261
|
</CardContent>
|
|
174
262
|
</Card>
|
|
175
263
|
|
|
176
264
|
<Card>
|
|
177
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-
|
|
178
|
-
<CardTitle className="text-
|
|
265
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
|
|
266
|
+
<CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
179
267
|
{t('cards.average')}
|
|
180
268
|
</CardTitle>
|
|
181
|
-
<
|
|
269
|
+
<div className="rounded-full bg-blue-500/10 p-1.5">
|
|
270
|
+
<Building2 className="h-4 w-4 text-blue-600" />
|
|
271
|
+
</div>
|
|
182
272
|
</CardHeader>
|
|
183
|
-
<CardContent>
|
|
184
|
-
<div className="text-
|
|
185
|
-
<Money value={average} />
|
|
273
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
274
|
+
<div className="text-xl font-bold">
|
|
275
|
+
<Money value={viewData.average} />
|
|
186
276
|
</div>
|
|
187
277
|
</CardContent>
|
|
188
278
|
</Card>
|
|
189
279
|
</div>
|
|
190
280
|
|
|
191
|
-
<div className="grid gap-
|
|
281
|
+
<div className="grid gap-3 xl:grid-cols-5">
|
|
192
282
|
<Card className="xl:col-span-3">
|
|
193
|
-
<CardHeader>
|
|
283
|
+
<CardHeader className="pb-3">
|
|
194
284
|
<CardTitle>{t('bars.title')}</CardTitle>
|
|
195
285
|
<CardDescription>{t('bars.description')}</CardDescription>
|
|
196
286
|
</CardHeader>
|
|
197
|
-
<CardContent>
|
|
198
|
-
<ResponsiveContainer width="100%" height={
|
|
287
|
+
<CardContent className="pb-4 pt-0">
|
|
288
|
+
<ResponsiveContainer width="100%" height={360}>
|
|
199
289
|
<BarChart
|
|
200
|
-
data={
|
|
290
|
+
data={viewData.topExpenses}
|
|
201
291
|
layout="vertical"
|
|
202
|
-
margin={{ left:
|
|
292
|
+
margin={{ left: 60, right: 16 }}
|
|
203
293
|
>
|
|
294
|
+
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
|
204
295
|
<XAxis
|
|
205
296
|
type="number"
|
|
206
297
|
tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
|
|
207
298
|
/>
|
|
208
|
-
<YAxis dataKey="
|
|
299
|
+
<YAxis dataKey="label" type="category" width={190} />
|
|
209
300
|
<Tooltip
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
style: 'currency',
|
|
213
|
-
currency: 'BRL',
|
|
214
|
-
}).format(value)
|
|
215
|
-
}
|
|
216
|
-
/>
|
|
217
|
-
<Bar
|
|
218
|
-
dataKey="value"
|
|
219
|
-
radius={[0, 6, 6, 0]}
|
|
220
|
-
fill="hsl(var(--chart-5))"
|
|
301
|
+
cursor={{ fill: 'hsl(var(--muted) / 0.35)' }}
|
|
302
|
+
content={renderSolidTooltip}
|
|
221
303
|
/>
|
|
304
|
+
<Bar dataKey="value" radius={[0, 6, 6, 0]}>
|
|
305
|
+
{viewData.topExpenses.map((entry, index) => (
|
|
306
|
+
<Cell
|
|
307
|
+
key={entry.label}
|
|
308
|
+
fill={rankingColors[index % rankingColors.length]}
|
|
309
|
+
stroke="hsl(var(--background))"
|
|
310
|
+
strokeWidth={1}
|
|
311
|
+
/>
|
|
312
|
+
))}
|
|
313
|
+
</Bar>
|
|
222
314
|
</BarChart>
|
|
223
315
|
</ResponsiveContainer>
|
|
316
|
+
<div className="mt-4 grid grid-cols-2 gap-2 text-sm sm:grid-cols-3">
|
|
317
|
+
{viewData.topExpenses.slice(0, 6).map((item, index) => (
|
|
318
|
+
<div key={item.label} className="flex items-center gap-2">
|
|
319
|
+
<span
|
|
320
|
+
className="inline-block h-2.5 w-2.5 rounded-full"
|
|
321
|
+
style={{
|
|
322
|
+
backgroundColor:
|
|
323
|
+
rankingColors[index % rankingColors.length],
|
|
324
|
+
}}
|
|
325
|
+
/>
|
|
326
|
+
<span className="truncate text-foreground/90">
|
|
327
|
+
#{index + 1} {item.category}
|
|
328
|
+
</span>
|
|
329
|
+
</div>
|
|
330
|
+
))}
|
|
331
|
+
</div>
|
|
224
332
|
</CardContent>
|
|
225
333
|
</Card>
|
|
226
334
|
|
|
227
335
|
<Card className="xl:col-span-2">
|
|
228
|
-
<CardHeader>
|
|
336
|
+
<CardHeader className="pb-3">
|
|
229
337
|
<CardTitle>{t('pie.title')}</CardTitle>
|
|
230
338
|
<CardDescription>{t('pie.description')}</CardDescription>
|
|
231
339
|
</CardHeader>
|
|
232
|
-
<CardContent>
|
|
233
|
-
<ResponsiveContainer width="100%" height={
|
|
340
|
+
<CardContent className="pb-4 pt-0">
|
|
341
|
+
<ResponsiveContainer width="100%" height={340}>
|
|
234
342
|
<PieChart>
|
|
235
343
|
<Pie
|
|
236
|
-
data={
|
|
344
|
+
data={viewData.pieData}
|
|
237
345
|
dataKey="value"
|
|
238
346
|
nameKey="name"
|
|
239
|
-
innerRadius={
|
|
240
|
-
outerRadius={
|
|
347
|
+
innerRadius={55}
|
|
348
|
+
outerRadius={100}
|
|
241
349
|
paddingAngle={2}
|
|
350
|
+
cy="42%"
|
|
242
351
|
>
|
|
243
|
-
{
|
|
352
|
+
{viewData.pieData.map((entry, index) => (
|
|
244
353
|
<Cell
|
|
245
354
|
key={entry.name}
|
|
246
|
-
fill={
|
|
355
|
+
fill={rankingColors[index % rankingColors.length]}
|
|
356
|
+
stroke="hsl(var(--background))"
|
|
357
|
+
strokeWidth={1}
|
|
247
358
|
/>
|
|
248
359
|
))}
|
|
249
360
|
</Pie>
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
361
|
+
<Legend
|
|
362
|
+
iconType="circle"
|
|
363
|
+
verticalAlign="bottom"
|
|
364
|
+
wrapperStyle={{
|
|
365
|
+
fontSize: 12,
|
|
366
|
+
lineHeight: '20px',
|
|
367
|
+
paddingTop: 12,
|
|
368
|
+
}}
|
|
257
369
|
/>
|
|
370
|
+
<Tooltip cursor={false} content={renderSolidTooltip} />
|
|
258
371
|
</PieChart>
|
|
259
372
|
</ResponsiveContainer>
|
|
260
373
|
</CardContent>
|
|
@@ -281,7 +394,7 @@ export default function TopOperationalExpensesReportPage() {
|
|
|
281
394
|
</TableRow>
|
|
282
395
|
</TableHeader>
|
|
283
396
|
<TableBody>
|
|
284
|
-
{
|
|
397
|
+
{viewData.topExpenses.length === 0 ? (
|
|
285
398
|
<TableRow>
|
|
286
399
|
<TableCell
|
|
287
400
|
colSpan={4}
|
|
@@ -291,9 +404,11 @@ export default function TopOperationalExpensesReportPage() {
|
|
|
291
404
|
</TableCell>
|
|
292
405
|
</TableRow>
|
|
293
406
|
) : (
|
|
294
|
-
|
|
407
|
+
viewData.topExpenses.map((item) => {
|
|
295
408
|
const participation =
|
|
296
|
-
|
|
409
|
+
viewData.total > 0
|
|
410
|
+
? (item.value / viewData.total) * 100
|
|
411
|
+
: 0;
|
|
297
412
|
|
|
298
413
|
return (
|
|
299
414
|
<TableRow key={item.category}>
|
|
@@ -277,6 +277,10 @@
|
|
|
277
277
|
"openAttachment": "Open attachment"
|
|
278
278
|
}
|
|
279
279
|
},
|
|
280
|
+
"empty": {
|
|
281
|
+
"title": "No titles found",
|
|
282
|
+
"description": "Create the first title to start managing accounts payable."
|
|
283
|
+
},
|
|
280
284
|
"dialogs": {
|
|
281
285
|
"cancel": {
|
|
282
286
|
"title": "Confirm cancellation",
|
|
@@ -570,6 +574,11 @@
|
|
|
570
574
|
"total": "Total",
|
|
571
575
|
"actions": "Actions"
|
|
572
576
|
}
|
|
577
|
+
},
|
|
578
|
+
"empty": {
|
|
579
|
+
"title": "No default data found",
|
|
580
|
+
"description": "There are no late clients for the current filters.",
|
|
581
|
+
"action": "Refresh data"
|
|
573
582
|
}
|
|
574
583
|
},
|
|
575
584
|
"ReceivableInstallmentsPage": {
|
|
@@ -995,6 +1004,10 @@
|
|
|
995
1004
|
"reconciliation": "Reconciliation"
|
|
996
1005
|
}
|
|
997
1006
|
},
|
|
1007
|
+
"empty": {
|
|
1008
|
+
"title": "No transactions found",
|
|
1009
|
+
"description": "Import a statement to view account transactions."
|
|
1010
|
+
},
|
|
998
1011
|
"types": {
|
|
999
1012
|
"inflow": "Inflow",
|
|
1000
1013
|
"outflow": "Outflow"
|
|
@@ -1651,7 +1664,16 @@
|
|
|
1651
1664
|
"description": "Select period and grouping level",
|
|
1652
1665
|
"from": "From",
|
|
1653
1666
|
"to": "To",
|
|
1654
|
-
"groupBy": "Group by"
|
|
1667
|
+
"groupBy": "Group by",
|
|
1668
|
+
"fromPlaceholder": "From date",
|
|
1669
|
+
"toPlaceholder": "To date",
|
|
1670
|
+
"groupByPlaceholder": "Group",
|
|
1671
|
+
"searchPlaceholder": "Search customer, supplier, category...",
|
|
1672
|
+
"fromAria": "Start date filter",
|
|
1673
|
+
"toAria": "End date filter",
|
|
1674
|
+
"groupByAria": "Grouping filter",
|
|
1675
|
+
"searchAria": "Search filter by customer",
|
|
1676
|
+
"submit": "Apply"
|
|
1655
1677
|
},
|
|
1656
1678
|
"groupBy": {
|
|
1657
1679
|
"day": "Day",
|
|
@@ -1698,7 +1720,16 @@
|
|
|
1698
1720
|
"description": "Select period and grouping level",
|
|
1699
1721
|
"from": "From",
|
|
1700
1722
|
"to": "To",
|
|
1701
|
-
"groupBy": "Group by"
|
|
1723
|
+
"groupBy": "Group by",
|
|
1724
|
+
"fromPlaceholder": "From date",
|
|
1725
|
+
"toPlaceholder": "To date",
|
|
1726
|
+
"groupByPlaceholder": "Group",
|
|
1727
|
+
"searchPlaceholder": "Search customer, supplier, category...",
|
|
1728
|
+
"fromAria": "Start date filter",
|
|
1729
|
+
"toAria": "End date filter",
|
|
1730
|
+
"groupByAria": "Grouping filter",
|
|
1731
|
+
"searchAria": "Search filter by category or cost center",
|
|
1732
|
+
"submit": "Apply"
|
|
1702
1733
|
},
|
|
1703
1734
|
"groupBy": {
|
|
1704
1735
|
"day": "Day",
|
|
@@ -277,6 +277,10 @@
|
|
|
277
277
|
"openAttachment": "Abrir anexo"
|
|
278
278
|
}
|
|
279
279
|
},
|
|
280
|
+
"empty": {
|
|
281
|
+
"title": "Nenhum título encontrado",
|
|
282
|
+
"description": "Cadastre o primeiro título para começar a gerenciar contas a pagar."
|
|
283
|
+
},
|
|
280
284
|
"dialogs": {
|
|
281
285
|
"cancel": {
|
|
282
286
|
"title": "Confirmar cancelamento",
|
|
@@ -570,6 +574,11 @@
|
|
|
570
574
|
"total": "Total",
|
|
571
575
|
"actions": "Ações"
|
|
572
576
|
}
|
|
577
|
+
},
|
|
578
|
+
"empty": {
|
|
579
|
+
"title": "Nenhum dado de inadimplência",
|
|
580
|
+
"description": "Não há clientes inadimplentes para os filtros atuais.",
|
|
581
|
+
"action": "Atualizar dados"
|
|
573
582
|
}
|
|
574
583
|
},
|
|
575
584
|
"ReceivableInstallmentsPage": {
|
|
@@ -995,6 +1004,10 @@
|
|
|
995
1004
|
"reconciliation": "Conciliação"
|
|
996
1005
|
}
|
|
997
1006
|
},
|
|
1007
|
+
"empty": {
|
|
1008
|
+
"title": "Nenhuma movimentação encontrada",
|
|
1009
|
+
"description": "Importe um extrato para visualizar as movimentações desta conta."
|
|
1010
|
+
},
|
|
998
1011
|
"types": {
|
|
999
1012
|
"inflow": "Entrada",
|
|
1000
1013
|
"outflow": "Saída"
|
|
@@ -1651,7 +1664,16 @@
|
|
|
1651
1664
|
"description": "Selecione o período e o nível de agrupamento",
|
|
1652
1665
|
"from": "Data de",
|
|
1653
1666
|
"to": "Data até",
|
|
1654
|
-
"groupBy": "Agrupar por"
|
|
1667
|
+
"groupBy": "Agrupar por",
|
|
1668
|
+
"fromPlaceholder": "Data de",
|
|
1669
|
+
"toPlaceholder": "Data até",
|
|
1670
|
+
"groupByPlaceholder": "Agrupar",
|
|
1671
|
+
"searchPlaceholder": "Buscar cliente, fornecedor, categoria...",
|
|
1672
|
+
"fromAria": "Filtro de data inicial",
|
|
1673
|
+
"toAria": "Filtro de data final",
|
|
1674
|
+
"groupByAria": "Filtro de agrupamento",
|
|
1675
|
+
"searchAria": "Filtro de busca por cliente",
|
|
1676
|
+
"submit": "Aplicar"
|
|
1655
1677
|
},
|
|
1656
1678
|
"groupBy": {
|
|
1657
1679
|
"day": "Dia",
|
|
@@ -1698,7 +1720,16 @@
|
|
|
1698
1720
|
"description": "Selecione o período e o nível de agrupamento",
|
|
1699
1721
|
"from": "Data de",
|
|
1700
1722
|
"to": "Data até",
|
|
1701
|
-
"groupBy": "Agrupar por"
|
|
1723
|
+
"groupBy": "Agrupar por",
|
|
1724
|
+
"fromPlaceholder": "Data de",
|
|
1725
|
+
"toPlaceholder": "Data até",
|
|
1726
|
+
"groupByPlaceholder": "Agrupar",
|
|
1727
|
+
"searchPlaceholder": "Buscar cliente, fornecedor, categoria...",
|
|
1728
|
+
"fromAria": "Filtro de data inicial",
|
|
1729
|
+
"toAria": "Filtro de data final",
|
|
1730
|
+
"groupByAria": "Filtro de agrupamento",
|
|
1731
|
+
"searchAria": "Filtro de busca por categoria ou centro de custo",
|
|
1732
|
+
"submit": "Aplicar"
|
|
1702
1733
|
},
|
|
1703
1734
|
"groupBy": {
|
|
1704
1735
|
"day": "Dia",
|