@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.
Files changed (35) hide show
  1. package/dist/dto/finance-report-query.dto.d.ts +16 -0
  2. package/dist/dto/finance-report-query.dto.d.ts.map +1 -0
  3. package/dist/dto/finance-report-query.dto.js +59 -0
  4. package/dist/dto/finance-report-query.dto.js.map +1 -0
  5. package/dist/finance-reports.controller.d.ts +71 -0
  6. package/dist/finance-reports.controller.d.ts.map +1 -0
  7. package/dist/finance-reports.controller.js +61 -0
  8. package/dist/finance-reports.controller.js.map +1 -0
  9. package/dist/finance.module.d.ts.map +1 -1
  10. package/dist/finance.module.js +2 -0
  11. package/dist/finance.module.js.map +1 -1
  12. package/dist/finance.service.d.ts +93 -0
  13. package/dist/finance.service.d.ts.map +1 -1
  14. package/dist/finance.service.js +456 -0
  15. package/dist/finance.service.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/hedhog/data/route.yaml +27 -0
  21. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +158 -125
  22. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +102 -88
  23. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +113 -89
  24. package/hedhog/frontend/app/reports/_lib/use-finance-reports.ts.ejs +233 -0
  25. package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +96 -78
  26. package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +247 -130
  27. package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +250 -135
  28. package/hedhog/frontend/messages/en.json +33 -2
  29. package/hedhog/frontend/messages/pt.json +33 -2
  30. package/package.json +6 -6
  31. package/src/dto/finance-report-query.dto.ts +49 -0
  32. package/src/finance-reports.controller.ts +28 -0
  33. package/src/finance.module.ts +2 -0
  34. package/src/finance.service.ts +645 -10
  35. 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 { useMemo, useState } from 'react';
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
- } from '../_lib/report-aggregations';
47
- import { type GroupBy } from '../_lib/report-mocks';
47
+ type GroupBy,
48
+ useTopOperationalExpensesReport,
49
+ } from '../_lib/use-finance-reports';
48
50
 
49
- const pieColors = [
50
- 'hsl(var(--chart-1))',
51
- 'hsl(var(--chart-2))',
52
- 'hsl(var(--chart-3))',
53
- 'hsl(var(--chart-4))',
54
- 'hsl(var(--chart-5))',
55
- '#64748B',
56
- '#0EA5E9',
57
- '#22C55E',
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 [from, setFrom] = useState(defaults.from);
65
- const [to, setTo] = useState(defaults.to);
66
- const [groupBy, setGroupBy] = useState<GroupBy>('year');
85
+ const handleSubmitFilters = (event: FormEvent<HTMLFormElement>) => {
86
+ event.preventDefault();
87
+ setAppliedFilters(filters);
88
+ };
67
89
 
68
- const data = useMemo(
69
- () =>
70
- aggregateOperationalExpenses({
71
- from,
72
- to,
73
- groupBy,
74
- topN: 20,
75
- }),
76
- [from, to, groupBy]
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
- const highest = data.topExpenses[0];
80
- const average =
81
- data.topExpenses.length > 0
82
- ? data.topExpenses.reduce((acc, item) => acc + item.value, 0) /
83
- data.topExpenses.length
84
- : 0;
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
- <Card>
99
- <CardHeader>
100
- <CardTitle>{t('filters.title')}</CardTitle>
101
- <CardDescription>{t('filters.description')}</CardDescription>
102
- </CardHeader>
103
- <CardContent className="grid gap-4 md:grid-cols-3">
104
- <div className="space-y-2">
105
- <Label htmlFor="from">{t('filters.from')}</Label>
106
- <Input
107
- id="from"
108
- type="date"
109
- value={from}
110
- onChange={(event) => setFrom(event.target.value)}
111
- max={to}
112
- />
113
- </div>
114
- <div className="space-y-2">
115
- <Label htmlFor="to">{t('filters.to')}</Label>
116
- <Input
117
- id="to"
118
- type="date"
119
- value={to}
120
- onChange={(event) => setTo(event.target.value)}
121
- min={from}
122
- />
123
- </div>
124
- <div className="space-y-2">
125
- <Label>{t('filters.groupBy')}</Label>
126
- <Select
127
- value={groupBy}
128
- onValueChange={(value) => setGroupBy(value as GroupBy)}
129
- >
130
- <SelectTrigger>
131
- <SelectValue />
132
- </SelectTrigger>
133
- <SelectContent>
134
- <SelectItem value="day">{t('groupBy.day')}</SelectItem>
135
- <SelectItem value="week">{t('groupBy.week')}</SelectItem>
136
- <SelectItem value="month">{t('groupBy.month')}</SelectItem>
137
- <SelectItem value="year">{t('groupBy.year')}</SelectItem>
138
- </SelectContent>
139
- </Select>
140
- </div>
141
- </CardContent>
142
- </Card>
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-4 md:grid-cols-3">
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-2">
147
- <CardTitle className="text-sm font-medium">
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
- <CircleDollarSign className="h-4 w-4 text-muted-foreground" />
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-2xl font-bold">
154
- <Money value={data.total} />
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-2">
161
- <CardTitle className="text-sm font-medium">
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
- <TriangleAlert className="h-4 w-4 text-amber-500" />
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-lg font-semibold">
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 text-sm">
171
- {highest ? <Money value={highest.value} /> : '-'}
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-2">
178
- <CardTitle className="text-sm font-medium">
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
- <Building2 className="h-4 w-4 text-muted-foreground" />
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-2xl font-bold">
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-4 xl:grid-cols-5">
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={560}>
287
+ <CardContent className="pb-4 pt-0">
288
+ <ResponsiveContainer width="100%" height={360}>
199
289
  <BarChart
200
- data={[...data.topExpenses].reverse()}
290
+ data={viewData.topExpenses}
201
291
  layout="vertical"
202
- margin={{ left: 80, right: 16 }}
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="category" type="category" width={210} />
299
+ <YAxis dataKey="label" type="category" width={190} />
209
300
  <Tooltip
210
- formatter={(value: number) =>
211
- new Intl.NumberFormat('pt-BR', {
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={380}>
340
+ <CardContent className="pb-4 pt-0">
341
+ <ResponsiveContainer width="100%" height={340}>
234
342
  <PieChart>
235
343
  <Pie
236
- data={data.pieData}
344
+ data={viewData.pieData}
237
345
  dataKey="value"
238
346
  nameKey="name"
239
- innerRadius={65}
240
- outerRadius={120}
347
+ innerRadius={55}
348
+ outerRadius={100}
241
349
  paddingAngle={2}
350
+ cy="42%"
242
351
  >
243
- {data.pieData.map((entry, index) => (
352
+ {viewData.pieData.map((entry, index) => (
244
353
  <Cell
245
354
  key={entry.name}
246
- fill={pieColors[index % pieColors.length]}
355
+ fill={rankingColors[index % rankingColors.length]}
356
+ stroke="hsl(var(--background))"
357
+ strokeWidth={1}
247
358
  />
248
359
  ))}
249
360
  </Pie>
250
- <Tooltip
251
- formatter={(value: number) =>
252
- new Intl.NumberFormat('pt-BR', {
253
- style: 'currency',
254
- currency: 'BRL',
255
- }).format(value)
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
- {data.topExpenses.length === 0 ? (
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
- data.topExpenses.map((item) => {
407
+ viewData.topExpenses.map((item) => {
295
408
  const participation =
296
- data.total > 0 ? (item.value / data.total) * 100 : 0;
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",