@hed-hog/finance 0.0.278 → 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/README.md +65 -29
- 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/menu.yaml +46 -0
- 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/report-aggregations.ts.ejs +275 -0
- package/hedhog/frontend/app/reports/_lib/report-mocks.ts.ejs +186 -0
- package/hedhog/frontend/app/reports/_lib/use-finance-reports.ts.ejs +233 -0
- package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +355 -0
- package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +427 -0
- package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +433 -0
- package/hedhog/frontend/messages/en.json +179 -0
- package/hedhog/frontend/messages/pt.json +179 -0
- package/package.json +7 -7
- 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
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Page, PageHeader } from '@/components/entity-list';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from '@/components/ui/card';
|
|
12
|
+
import { Input } from '@/components/ui/input';
|
|
13
|
+
import { Money } from '@/components/ui/money';
|
|
14
|
+
import {
|
|
15
|
+
Select,
|
|
16
|
+
SelectContent,
|
|
17
|
+
SelectItem,
|
|
18
|
+
SelectTrigger,
|
|
19
|
+
SelectValue,
|
|
20
|
+
} from '@/components/ui/select';
|
|
21
|
+
import {
|
|
22
|
+
Table,
|
|
23
|
+
TableBody,
|
|
24
|
+
TableCell,
|
|
25
|
+
TableHead,
|
|
26
|
+
TableHeader,
|
|
27
|
+
TableRow,
|
|
28
|
+
} from '@/components/ui/table';
|
|
29
|
+
import { Building2, CircleDollarSign, TriangleAlert } from 'lucide-react';
|
|
30
|
+
import { useTranslations } from 'next-intl';
|
|
31
|
+
import { type FormEvent, useState } from 'react';
|
|
32
|
+
import {
|
|
33
|
+
Bar,
|
|
34
|
+
BarChart,
|
|
35
|
+
CartesianGrid,
|
|
36
|
+
Cell,
|
|
37
|
+
Legend,
|
|
38
|
+
Pie,
|
|
39
|
+
PieChart,
|
|
40
|
+
ResponsiveContainer,
|
|
41
|
+
Tooltip,
|
|
42
|
+
XAxis,
|
|
43
|
+
YAxis,
|
|
44
|
+
} from 'recharts';
|
|
45
|
+
import {
|
|
46
|
+
getDefaultDateRange,
|
|
47
|
+
type GroupBy,
|
|
48
|
+
useTopOperationalExpensesReport,
|
|
49
|
+
} from '../_lib/use-finance-reports';
|
|
50
|
+
|
|
51
|
+
const rankingColors = [
|
|
52
|
+
'#2563EB',
|
|
53
|
+
'#DC2626',
|
|
54
|
+
'#059669',
|
|
55
|
+
'#D97706',
|
|
56
|
+
'#7C3AED',
|
|
57
|
+
'#0F766E',
|
|
58
|
+
'#BE123C',
|
|
59
|
+
'#1D4ED8',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const currencyFormatter = new Intl.NumberFormat('pt-BR', {
|
|
63
|
+
style: 'currency',
|
|
64
|
+
currency: 'BRL',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export default function TopOperationalExpensesReportPage() {
|
|
68
|
+
const t = useTranslations('finance.TopOperationalExpensesReportPage');
|
|
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
|
+
});
|
|
84
|
+
|
|
85
|
+
const handleSubmitFilters = (event: FormEvent<HTMLFormElement>) => {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
setAppliedFilters(filters);
|
|
88
|
+
};
|
|
89
|
+
|
|
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
|
+
}
|
|
107
|
+
|
|
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
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<Page>
|
|
136
|
+
<PageHeader
|
|
137
|
+
title={t('header.title')}
|
|
138
|
+
description={t('header.description')}
|
|
139
|
+
breadcrumbs={[
|
|
140
|
+
{ label: t('breadcrumbs.home'), href: '/' },
|
|
141
|
+
{ label: t('breadcrumbs.finance'), href: '/finance' },
|
|
142
|
+
{ label: t('breadcrumbs.current') },
|
|
143
|
+
]}
|
|
144
|
+
/>
|
|
145
|
+
|
|
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>
|
|
223
|
+
|
|
224
|
+
<div className="grid gap-3 md:grid-cols-3">
|
|
225
|
+
<Card>
|
|
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">
|
|
228
|
+
{t('cards.total')}
|
|
229
|
+
</CardTitle>
|
|
230
|
+
<div className="rounded-full bg-orange-500/10 p-1.5">
|
|
231
|
+
<CircleDollarSign className="h-4 w-4 text-orange-600" />
|
|
232
|
+
</div>
|
|
233
|
+
</CardHeader>
|
|
234
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
235
|
+
<div className="text-xl font-bold">
|
|
236
|
+
<Money value={viewData.total} />
|
|
237
|
+
</div>
|
|
238
|
+
</CardContent>
|
|
239
|
+
</Card>
|
|
240
|
+
|
|
241
|
+
<Card>
|
|
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">
|
|
244
|
+
{t('cards.highestExpense')}
|
|
245
|
+
</CardTitle>
|
|
246
|
+
<div className="rounded-full bg-amber-500/10 p-1.5">
|
|
247
|
+
<TriangleAlert className="h-4 w-4 text-amber-500" />
|
|
248
|
+
</div>
|
|
249
|
+
</CardHeader>
|
|
250
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
251
|
+
<div className="text-sm font-medium">
|
|
252
|
+
{viewData.highest?.category || '-'}
|
|
253
|
+
</div>
|
|
254
|
+
<div className="text-xs text-muted-foreground">
|
|
255
|
+
{viewData.highest ? (
|
|
256
|
+
<Money value={viewData.highest.value} />
|
|
257
|
+
) : (
|
|
258
|
+
'-'
|
|
259
|
+
)}
|
|
260
|
+
</div>
|
|
261
|
+
</CardContent>
|
|
262
|
+
</Card>
|
|
263
|
+
|
|
264
|
+
<Card>
|
|
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">
|
|
267
|
+
{t('cards.average')}
|
|
268
|
+
</CardTitle>
|
|
269
|
+
<div className="rounded-full bg-blue-500/10 p-1.5">
|
|
270
|
+
<Building2 className="h-4 w-4 text-blue-600" />
|
|
271
|
+
</div>
|
|
272
|
+
</CardHeader>
|
|
273
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
274
|
+
<div className="text-xl font-bold">
|
|
275
|
+
<Money value={viewData.average} />
|
|
276
|
+
</div>
|
|
277
|
+
</CardContent>
|
|
278
|
+
</Card>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div className="grid gap-3 xl:grid-cols-5">
|
|
282
|
+
<Card className="xl:col-span-3">
|
|
283
|
+
<CardHeader className="pb-3">
|
|
284
|
+
<CardTitle>{t('bars.title')}</CardTitle>
|
|
285
|
+
<CardDescription>{t('bars.description')}</CardDescription>
|
|
286
|
+
</CardHeader>
|
|
287
|
+
<CardContent className="pb-4 pt-0">
|
|
288
|
+
<ResponsiveContainer width="100%" height={360}>
|
|
289
|
+
<BarChart
|
|
290
|
+
data={viewData.topExpenses}
|
|
291
|
+
layout="vertical"
|
|
292
|
+
margin={{ left: 60, right: 16 }}
|
|
293
|
+
>
|
|
294
|
+
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
|
295
|
+
<XAxis
|
|
296
|
+
type="number"
|
|
297
|
+
tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
|
|
298
|
+
/>
|
|
299
|
+
<YAxis dataKey="label" type="category" width={190} />
|
|
300
|
+
<Tooltip
|
|
301
|
+
cursor={{ fill: 'hsl(var(--muted) / 0.35)' }}
|
|
302
|
+
content={renderSolidTooltip}
|
|
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>
|
|
314
|
+
</BarChart>
|
|
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>
|
|
332
|
+
</CardContent>
|
|
333
|
+
</Card>
|
|
334
|
+
|
|
335
|
+
<Card className="xl:col-span-2">
|
|
336
|
+
<CardHeader className="pb-3">
|
|
337
|
+
<CardTitle>{t('pie.title')}</CardTitle>
|
|
338
|
+
<CardDescription>{t('pie.description')}</CardDescription>
|
|
339
|
+
</CardHeader>
|
|
340
|
+
<CardContent className="pb-4 pt-0">
|
|
341
|
+
<ResponsiveContainer width="100%" height={340}>
|
|
342
|
+
<PieChart>
|
|
343
|
+
<Pie
|
|
344
|
+
data={viewData.pieData}
|
|
345
|
+
dataKey="value"
|
|
346
|
+
nameKey="name"
|
|
347
|
+
innerRadius={55}
|
|
348
|
+
outerRadius={100}
|
|
349
|
+
paddingAngle={2}
|
|
350
|
+
cy="42%"
|
|
351
|
+
>
|
|
352
|
+
{viewData.pieData.map((entry, index) => (
|
|
353
|
+
<Cell
|
|
354
|
+
key={entry.name}
|
|
355
|
+
fill={rankingColors[index % rankingColors.length]}
|
|
356
|
+
stroke="hsl(var(--background))"
|
|
357
|
+
strokeWidth={1}
|
|
358
|
+
/>
|
|
359
|
+
))}
|
|
360
|
+
</Pie>
|
|
361
|
+
<Legend
|
|
362
|
+
iconType="circle"
|
|
363
|
+
verticalAlign="bottom"
|
|
364
|
+
wrapperStyle={{
|
|
365
|
+
fontSize: 12,
|
|
366
|
+
lineHeight: '20px',
|
|
367
|
+
paddingTop: 12,
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
370
|
+
<Tooltip cursor={false} content={renderSolidTooltip} />
|
|
371
|
+
</PieChart>
|
|
372
|
+
</ResponsiveContainer>
|
|
373
|
+
</CardContent>
|
|
374
|
+
</Card>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<Card>
|
|
378
|
+
<CardHeader>
|
|
379
|
+
<CardTitle>{t('table.title')}</CardTitle>
|
|
380
|
+
<CardDescription>{t('table.description')}</CardDescription>
|
|
381
|
+
</CardHeader>
|
|
382
|
+
<CardContent>
|
|
383
|
+
<Table>
|
|
384
|
+
<TableHeader>
|
|
385
|
+
<TableRow>
|
|
386
|
+
<TableHead>{t('table.headers.category')}</TableHead>
|
|
387
|
+
<TableHead>{t('table.headers.costCenter')}</TableHead>
|
|
388
|
+
<TableHead className="text-right">
|
|
389
|
+
{t('table.headers.value')}
|
|
390
|
+
</TableHead>
|
|
391
|
+
<TableHead className="text-right">
|
|
392
|
+
{t('table.headers.participation')}
|
|
393
|
+
</TableHead>
|
|
394
|
+
</TableRow>
|
|
395
|
+
</TableHeader>
|
|
396
|
+
<TableBody>
|
|
397
|
+
{viewData.topExpenses.length === 0 ? (
|
|
398
|
+
<TableRow>
|
|
399
|
+
<TableCell
|
|
400
|
+
colSpan={4}
|
|
401
|
+
className="text-muted-foreground text-center"
|
|
402
|
+
>
|
|
403
|
+
{t('table.empty')}
|
|
404
|
+
</TableCell>
|
|
405
|
+
</TableRow>
|
|
406
|
+
) : (
|
|
407
|
+
viewData.topExpenses.map((item) => {
|
|
408
|
+
const participation =
|
|
409
|
+
viewData.total > 0
|
|
410
|
+
? (item.value / viewData.total) * 100
|
|
411
|
+
: 0;
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<TableRow key={item.category}>
|
|
415
|
+
<TableCell>{item.category}</TableCell>
|
|
416
|
+
<TableCell>{item.costCenter}</TableCell>
|
|
417
|
+
<TableCell className="text-right">
|
|
418
|
+
<Money value={item.value} />
|
|
419
|
+
</TableCell>
|
|
420
|
+
<TableCell className="text-right">
|
|
421
|
+
{participation.toFixed(2)}%
|
|
422
|
+
</TableCell>
|
|
423
|
+
</TableRow>
|
|
424
|
+
);
|
|
425
|
+
})
|
|
426
|
+
)}
|
|
427
|
+
</TableBody>
|
|
428
|
+
</Table>
|
|
429
|
+
</CardContent>
|
|
430
|
+
</Card>
|
|
431
|
+
</Page>
|
|
432
|
+
);
|
|
433
|
+
}
|
|
@@ -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"
|
|
@@ -1582,5 +1595,171 @@
|
|
|
1582
1595
|
"negative": "Negative",
|
|
1583
1596
|
"withGrowth": "With growth of {value}%"
|
|
1584
1597
|
}
|
|
1598
|
+
},
|
|
1599
|
+
"OverviewResultsReportPage": {
|
|
1600
|
+
"header": {
|
|
1601
|
+
"title": "Results Overview",
|
|
1602
|
+
"description": "Compare revenue and expenses in the selected period"
|
|
1603
|
+
},
|
|
1604
|
+
"breadcrumbs": {
|
|
1605
|
+
"home": "Home",
|
|
1606
|
+
"finance": "Finance",
|
|
1607
|
+
"current": "Results Overview"
|
|
1608
|
+
},
|
|
1609
|
+
"filters": {
|
|
1610
|
+
"title": "Filters",
|
|
1611
|
+
"description": "Set period and grouping for analysis",
|
|
1612
|
+
"from": "From",
|
|
1613
|
+
"to": "To",
|
|
1614
|
+
"groupBy": "Group by"
|
|
1615
|
+
},
|
|
1616
|
+
"groupBy": {
|
|
1617
|
+
"day": "Day",
|
|
1618
|
+
"week": "Week",
|
|
1619
|
+
"month": "Month",
|
|
1620
|
+
"year": "Year"
|
|
1621
|
+
},
|
|
1622
|
+
"cards": {
|
|
1623
|
+
"revenue": "Revenue",
|
|
1624
|
+
"expensesAndLoans": "Expenses + Loans",
|
|
1625
|
+
"resultDifference": "Result Difference",
|
|
1626
|
+
"margin": "Margin"
|
|
1627
|
+
},
|
|
1628
|
+
"status": {
|
|
1629
|
+
"positive": "Positive Result",
|
|
1630
|
+
"negative": "Negative Result"
|
|
1631
|
+
},
|
|
1632
|
+
"chart": {
|
|
1633
|
+
"title": "Results Comparison",
|
|
1634
|
+
"description": "Result evolution by grouped period",
|
|
1635
|
+
"revenueLabel": "Revenue",
|
|
1636
|
+
"expensesLabel": "Expenses + Loans",
|
|
1637
|
+
"differenceLabel": "Difference"
|
|
1638
|
+
},
|
|
1639
|
+
"table": {
|
|
1640
|
+
"title": "Period Summary",
|
|
1641
|
+
"description": "Consolidated detail for selected range",
|
|
1642
|
+
"headers": {
|
|
1643
|
+
"period": "Period",
|
|
1644
|
+
"revenue": "Revenue",
|
|
1645
|
+
"expensesAndLoans": "Expenses + Loans",
|
|
1646
|
+
"investorContribution": "Investor Contribution",
|
|
1647
|
+
"difference": "Difference"
|
|
1648
|
+
},
|
|
1649
|
+
"empty": "No data found for selected filters"
|
|
1650
|
+
}
|
|
1651
|
+
},
|
|
1652
|
+
"TopCustomersReportPage": {
|
|
1653
|
+
"header": {
|
|
1654
|
+
"title": "Top Customers",
|
|
1655
|
+
"description": "Customer ranking for the selected period"
|
|
1656
|
+
},
|
|
1657
|
+
"breadcrumbs": {
|
|
1658
|
+
"home": "Home",
|
|
1659
|
+
"finance": "Finance",
|
|
1660
|
+
"current": "Top Customers"
|
|
1661
|
+
},
|
|
1662
|
+
"filters": {
|
|
1663
|
+
"title": "Filters",
|
|
1664
|
+
"description": "Select period and grouping level",
|
|
1665
|
+
"from": "From",
|
|
1666
|
+
"to": "To",
|
|
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"
|
|
1677
|
+
},
|
|
1678
|
+
"groupBy": {
|
|
1679
|
+
"day": "Day",
|
|
1680
|
+
"week": "Week",
|
|
1681
|
+
"month": "Month",
|
|
1682
|
+
"year": "Year"
|
|
1683
|
+
},
|
|
1684
|
+
"cards": {
|
|
1685
|
+
"total": "Total in Period",
|
|
1686
|
+
"top5Concentration": "Top 5 Concentration",
|
|
1687
|
+
"leader": "Leading Customer"
|
|
1688
|
+
},
|
|
1689
|
+
"bars": {
|
|
1690
|
+
"title": "Top 20 Customers",
|
|
1691
|
+
"description": "Revenue ranking in selected period"
|
|
1692
|
+
},
|
|
1693
|
+
"pie": {
|
|
1694
|
+
"title": "Revenue Share",
|
|
1695
|
+
"description": "Distribution among customers"
|
|
1696
|
+
},
|
|
1697
|
+
"table": {
|
|
1698
|
+
"title": "Share Table",
|
|
1699
|
+
"description": "Individual customer contribution",
|
|
1700
|
+
"headers": {
|
|
1701
|
+
"customer": "Customer",
|
|
1702
|
+
"value": "Value",
|
|
1703
|
+
"participation": "Share"
|
|
1704
|
+
},
|
|
1705
|
+
"empty": "No customers found for selected filters"
|
|
1706
|
+
}
|
|
1707
|
+
},
|
|
1708
|
+
"TopOperationalExpensesReportPage": {
|
|
1709
|
+
"header": {
|
|
1710
|
+
"title": "Top Operational Expenses",
|
|
1711
|
+
"description": "Track the most relevant expenses by category"
|
|
1712
|
+
},
|
|
1713
|
+
"breadcrumbs": {
|
|
1714
|
+
"home": "Home",
|
|
1715
|
+
"finance": "Finance",
|
|
1716
|
+
"current": "Top Operational Expenses"
|
|
1717
|
+
},
|
|
1718
|
+
"filters": {
|
|
1719
|
+
"title": "Filters",
|
|
1720
|
+
"description": "Select period and grouping level",
|
|
1721
|
+
"from": "From",
|
|
1722
|
+
"to": "To",
|
|
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"
|
|
1733
|
+
},
|
|
1734
|
+
"groupBy": {
|
|
1735
|
+
"day": "Day",
|
|
1736
|
+
"week": "Week",
|
|
1737
|
+
"month": "Month",
|
|
1738
|
+
"year": "Year"
|
|
1739
|
+
},
|
|
1740
|
+
"cards": {
|
|
1741
|
+
"total": "Total Expenses",
|
|
1742
|
+
"highestExpense": "Highest Expense",
|
|
1743
|
+
"average": "Average (Top 20)"
|
|
1744
|
+
},
|
|
1745
|
+
"bars": {
|
|
1746
|
+
"title": "Top 20 Expenses",
|
|
1747
|
+
"description": "Largest values by category"
|
|
1748
|
+
},
|
|
1749
|
+
"pie": {
|
|
1750
|
+
"title": "Cost Center Composition",
|
|
1751
|
+
"description": "Operational expense distribution"
|
|
1752
|
+
},
|
|
1753
|
+
"table": {
|
|
1754
|
+
"title": "Expenses Table",
|
|
1755
|
+
"description": "Detail by category and cost center",
|
|
1756
|
+
"headers": {
|
|
1757
|
+
"category": "Category",
|
|
1758
|
+
"costCenter": "Cost Center",
|
|
1759
|
+
"value": "Value",
|
|
1760
|
+
"participation": "Share"
|
|
1761
|
+
},
|
|
1762
|
+
"empty": "No expenses found for selected filters"
|
|
1763
|
+
}
|
|
1585
1764
|
}
|
|
1586
1765
|
}
|