@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 { Crown, PieChartIcon, Users } 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,43 +43,95 @@ import {
|
|
|
41
43
|
YAxis,
|
|
42
44
|
} from 'recharts';
|
|
43
45
|
import {
|
|
44
|
-
aggregateTopCustomers,
|
|
45
46
|
getDefaultDateRange,
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
type GroupBy,
|
|
48
|
+
useTopCustomersReport,
|
|
49
|
+
} from '../_lib/use-finance-reports';
|
|
48
50
|
|
|
49
|
-
const
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'#
|
|
56
|
-
'#
|
|
57
|
-
'#
|
|
58
|
-
'#
|
|
59
|
-
'#
|
|
51
|
+
const rankingColors = [
|
|
52
|
+
'#2563EB',
|
|
53
|
+
'#DC2626',
|
|
54
|
+
'#059669',
|
|
55
|
+
'#D97706',
|
|
56
|
+
'#7C3AED',
|
|
57
|
+
'#0F766E',
|
|
58
|
+
'#BE123C',
|
|
59
|
+
'#1D4ED8',
|
|
60
|
+
'#15803D',
|
|
61
|
+
'#B45309',
|
|
60
62
|
];
|
|
61
63
|
|
|
64
|
+
const currencyFormatter = new Intl.NumberFormat('pt-BR', {
|
|
65
|
+
style: 'currency',
|
|
66
|
+
currency: 'BRL',
|
|
67
|
+
});
|
|
68
|
+
|
|
62
69
|
export default function TopCustomersReportPage() {
|
|
63
70
|
const t = useTranslations('finance.TopCustomersReportPage');
|
|
64
71
|
const defaults = getDefaultDateRange();
|
|
65
|
-
const [
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
const [filters, setFilters] = useState({
|
|
73
|
+
from: defaults.from,
|
|
74
|
+
to: defaults.to,
|
|
75
|
+
groupBy: 'year' as GroupBy,
|
|
76
|
+
search: '',
|
|
77
|
+
});
|
|
78
|
+
const [appliedFilters, setAppliedFilters] = useState(filters);
|
|
79
|
+
const { data: viewData, isFetching } = useTopCustomersReport({
|
|
80
|
+
from: appliedFilters.from,
|
|
81
|
+
to: appliedFilters.to,
|
|
82
|
+
groupBy: appliedFilters.groupBy,
|
|
83
|
+
search: appliedFilters.search,
|
|
84
|
+
topN: 20,
|
|
85
|
+
});
|
|
68
86
|
|
|
69
|
-
const
|
|
70
|
-
()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
to,
|
|
74
|
-
groupBy,
|
|
75
|
-
topN: 20,
|
|
76
|
-
}),
|
|
77
|
-
[from, to, groupBy]
|
|
78
|
-
);
|
|
87
|
+
const handleSubmitFilters = (event: FormEvent<HTMLFormElement>) => {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
setAppliedFilters(filters);
|
|
90
|
+
};
|
|
79
91
|
|
|
80
|
-
const
|
|
92
|
+
const renderSolidTooltip = ({
|
|
93
|
+
active,
|
|
94
|
+
payload,
|
|
95
|
+
label,
|
|
96
|
+
}: {
|
|
97
|
+
active?: boolean;
|
|
98
|
+
payload?: Array<{
|
|
99
|
+
name?: string;
|
|
100
|
+
value?: number | string;
|
|
101
|
+
color?: string;
|
|
102
|
+
dataKey?: string;
|
|
103
|
+
}>;
|
|
104
|
+
label?: string | number;
|
|
105
|
+
}) => {
|
|
106
|
+
if (!active || !payload || payload.length === 0) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className="min-w-52 rounded-md border bg-popover px-3 py-2 shadow-xl">
|
|
112
|
+
<p className="mb-1 text-sm font-semibold text-popover-foreground">
|
|
113
|
+
{String(label ?? payload[0]?.name ?? '')}
|
|
114
|
+
</p>
|
|
115
|
+
{payload.map((row, index) => (
|
|
116
|
+
<div
|
|
117
|
+
key={`${row.dataKey ?? row.name ?? 'item'}-${index}`}
|
|
118
|
+
className="flex items-center justify-between gap-3 text-sm"
|
|
119
|
+
>
|
|
120
|
+
<div className="flex items-center gap-2 text-popover-foreground">
|
|
121
|
+
<span
|
|
122
|
+
className="inline-block h-2.5 w-2.5 rounded-full"
|
|
123
|
+
style={{ backgroundColor: row.color || 'hsl(var(--chart-1))' }}
|
|
124
|
+
/>
|
|
125
|
+
<span>{row.name ?? row.dataKey ?? 'Valor'}</span>
|
|
126
|
+
</div>
|
|
127
|
+
<span className="font-semibold text-popover-foreground">
|
|
128
|
+
{currencyFormatter.format(Number(row.value || 0))}
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
))}
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
81
135
|
|
|
82
136
|
return (
|
|
83
137
|
<Page>
|
|
@@ -91,164 +145,225 @@ export default function TopCustomersReportPage() {
|
|
|
91
145
|
]}
|
|
92
146
|
/>
|
|
93
147
|
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
148
|
+
<form
|
|
149
|
+
onSubmit={handleSubmitFilters}
|
|
150
|
+
className="flex w-full flex-col gap-3 lg:flex-row"
|
|
151
|
+
>
|
|
152
|
+
<Input
|
|
153
|
+
type="search"
|
|
154
|
+
value={filters.search}
|
|
155
|
+
onChange={(event) =>
|
|
156
|
+
setFilters((current) => ({
|
|
157
|
+
...current,
|
|
158
|
+
search: event.target.value,
|
|
159
|
+
}))
|
|
160
|
+
}
|
|
161
|
+
placeholder={t('filters.searchPlaceholder')}
|
|
162
|
+
className="w-full lg:flex-[2.4]"
|
|
163
|
+
aria-label={t('filters.searchAria')}
|
|
164
|
+
/>
|
|
165
|
+
<Input
|
|
166
|
+
id="from"
|
|
167
|
+
type="date"
|
|
168
|
+
value={filters.from}
|
|
169
|
+
onChange={(event) =>
|
|
170
|
+
setFilters((current) => ({
|
|
171
|
+
...current,
|
|
172
|
+
from: event.target.value,
|
|
173
|
+
}))
|
|
174
|
+
}
|
|
175
|
+
max={filters.to}
|
|
176
|
+
className="w-full lg:w-[170px]"
|
|
177
|
+
aria-label={t('filters.fromAria')}
|
|
178
|
+
placeholder={t('filters.fromPlaceholder')}
|
|
179
|
+
/>
|
|
180
|
+
<Input
|
|
181
|
+
id="to"
|
|
182
|
+
type="date"
|
|
183
|
+
value={filters.to}
|
|
184
|
+
onChange={(event) =>
|
|
185
|
+
setFilters((current) => ({
|
|
186
|
+
...current,
|
|
187
|
+
to: event.target.value,
|
|
188
|
+
}))
|
|
189
|
+
}
|
|
190
|
+
min={filters.from}
|
|
191
|
+
className="w-full lg:w-[170px]"
|
|
192
|
+
aria-label={t('filters.toAria')}
|
|
193
|
+
placeholder={t('filters.toPlaceholder')}
|
|
194
|
+
/>
|
|
195
|
+
<Select
|
|
196
|
+
value={filters.groupBy}
|
|
197
|
+
onValueChange={(value) =>
|
|
198
|
+
setFilters((current) => ({
|
|
199
|
+
...current,
|
|
200
|
+
groupBy: value as GroupBy,
|
|
201
|
+
}))
|
|
202
|
+
}
|
|
203
|
+
>
|
|
204
|
+
<SelectTrigger
|
|
205
|
+
className="w-full lg:w-[150px]"
|
|
206
|
+
aria-label={t('filters.groupByAria')}
|
|
207
|
+
>
|
|
208
|
+
<SelectValue placeholder={t('filters.groupByPlaceholder')} />
|
|
209
|
+
</SelectTrigger>
|
|
210
|
+
<SelectContent>
|
|
211
|
+
<SelectItem value="day">{t('groupBy.day')}</SelectItem>
|
|
212
|
+
<SelectItem value="week">{t('groupBy.week')}</SelectItem>
|
|
213
|
+
<SelectItem value="month">{t('groupBy.month')}</SelectItem>
|
|
214
|
+
<SelectItem value="year">{t('groupBy.year')}</SelectItem>
|
|
215
|
+
</SelectContent>
|
|
216
|
+
</Select>
|
|
217
|
+
<Button
|
|
218
|
+
type="submit"
|
|
219
|
+
className="w-full lg:w-auto"
|
|
220
|
+
disabled={isFetching}
|
|
221
|
+
>
|
|
222
|
+
{t('filters.submit')}
|
|
223
|
+
</Button>
|
|
224
|
+
</form>
|
|
139
225
|
|
|
140
|
-
<div className="grid gap-
|
|
226
|
+
<div className="grid gap-3 md:grid-cols-3">
|
|
141
227
|
<Card>
|
|
142
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-
|
|
143
|
-
<CardTitle className="text-
|
|
228
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
|
|
229
|
+
<CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
144
230
|
{t('cards.total')}
|
|
145
231
|
</CardTitle>
|
|
146
|
-
<
|
|
232
|
+
<div className="rounded-full bg-blue-500/10 p-1.5">
|
|
233
|
+
<Users className="h-4 w-4 text-blue-600" />
|
|
234
|
+
</div>
|
|
147
235
|
</CardHeader>
|
|
148
|
-
<CardContent>
|
|
149
|
-
<div className="text-
|
|
150
|
-
<Money value={
|
|
236
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
237
|
+
<div className="text-xl font-bold">
|
|
238
|
+
<Money value={viewData.total} />
|
|
151
239
|
</div>
|
|
152
240
|
</CardContent>
|
|
153
241
|
</Card>
|
|
154
242
|
<Card>
|
|
155
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-
|
|
156
|
-
<CardTitle className="text-
|
|
243
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
|
|
244
|
+
<CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
157
245
|
{t('cards.top5Concentration')}
|
|
158
246
|
</CardTitle>
|
|
159
|
-
<
|
|
247
|
+
<div className="rounded-full bg-purple-500/10 p-1.5">
|
|
248
|
+
<PieChartIcon className="h-4 w-4 text-purple-600" />
|
|
249
|
+
</div>
|
|
160
250
|
</CardHeader>
|
|
161
|
-
<CardContent>
|
|
162
|
-
<div className="text-
|
|
163
|
-
{
|
|
251
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
252
|
+
<div className="text-xl font-bold">
|
|
253
|
+
{viewData.top5Percent.toFixed(1)}%
|
|
164
254
|
</div>
|
|
165
255
|
</CardContent>
|
|
166
256
|
</Card>
|
|
167
257
|
<Card>
|
|
168
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-
|
|
169
|
-
<CardTitle className="text-
|
|
258
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
|
|
259
|
+
<CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
170
260
|
{t('cards.leader')}
|
|
171
261
|
</CardTitle>
|
|
172
|
-
<
|
|
262
|
+
<div className="rounded-full bg-amber-500/10 p-1.5">
|
|
263
|
+
<Crown className="h-4 w-4 text-amber-500" />
|
|
264
|
+
</div>
|
|
173
265
|
</CardHeader>
|
|
174
|
-
<CardContent>
|
|
175
|
-
<div className="text-
|
|
176
|
-
{leader?.customer || '-'}
|
|
266
|
+
<CardContent className="pb-3 pt-0 px-4">
|
|
267
|
+
<div className="text-sm font-medium">
|
|
268
|
+
{viewData.leader?.customer || '-'}
|
|
177
269
|
</div>
|
|
178
|
-
<div className="text-muted-foreground
|
|
179
|
-
{leader ? <Money value={leader.value} /> : '-'}
|
|
270
|
+
<div className="text-xs text-muted-foreground">
|
|
271
|
+
{viewData.leader ? <Money value={viewData.leader.value} /> : '-'}
|
|
180
272
|
</div>
|
|
181
273
|
</CardContent>
|
|
182
274
|
</Card>
|
|
183
275
|
</div>
|
|
184
276
|
|
|
185
|
-
<div className="grid gap-
|
|
277
|
+
<div className="grid gap-3 xl:grid-cols-5">
|
|
186
278
|
<Card className="xl:col-span-3">
|
|
187
|
-
<CardHeader>
|
|
279
|
+
<CardHeader className="pb-3">
|
|
188
280
|
<CardTitle>{t('bars.title')}</CardTitle>
|
|
189
281
|
<CardDescription>{t('bars.description')}</CardDescription>
|
|
190
282
|
</CardHeader>
|
|
191
|
-
<CardContent>
|
|
192
|
-
<ResponsiveContainer width="100%" height={
|
|
283
|
+
<CardContent className="pb-4 pt-0">
|
|
284
|
+
<ResponsiveContainer width="100%" height={360}>
|
|
193
285
|
<BarChart
|
|
194
|
-
data={
|
|
286
|
+
data={viewData.topCustomers}
|
|
195
287
|
layout="vertical"
|
|
196
288
|
margin={{ left: 60, right: 16 }}
|
|
197
289
|
>
|
|
290
|
+
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
|
198
291
|
<XAxis
|
|
199
292
|
type="number"
|
|
200
293
|
tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
|
|
201
294
|
/>
|
|
202
295
|
<YAxis dataKey="customer" type="category" width={190} />
|
|
203
296
|
<Tooltip
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
style: 'currency',
|
|
207
|
-
currency: 'BRL',
|
|
208
|
-
}).format(value)
|
|
209
|
-
}
|
|
210
|
-
/>
|
|
211
|
-
<Bar
|
|
212
|
-
dataKey="value"
|
|
213
|
-
radius={[0, 6, 6, 0]}
|
|
214
|
-
fill="hsl(var(--chart-2))"
|
|
297
|
+
cursor={{ fill: 'hsl(var(--muted) / 0.35)' }}
|
|
298
|
+
content={renderSolidTooltip}
|
|
215
299
|
/>
|
|
300
|
+
<Bar dataKey="value" radius={[0, 6, 6, 0]}>
|
|
301
|
+
{viewData.topCustomers.map((entry, index) => (
|
|
302
|
+
<Cell
|
|
303
|
+
key={entry.customer}
|
|
304
|
+
fill={rankingColors[index % rankingColors.length]}
|
|
305
|
+
stroke="hsl(var(--background))"
|
|
306
|
+
strokeWidth={1}
|
|
307
|
+
/>
|
|
308
|
+
))}
|
|
309
|
+
</Bar>
|
|
216
310
|
</BarChart>
|
|
217
311
|
</ResponsiveContainer>
|
|
312
|
+
<div className="mt-4 grid grid-cols-2 gap-2 text-sm sm:grid-cols-3">
|
|
313
|
+
{viewData.topCustomers.slice(0, 6).map((item, index) => (
|
|
314
|
+
<div key={item.customer} className="flex items-center gap-2">
|
|
315
|
+
<span
|
|
316
|
+
className="inline-block h-2.5 w-2.5 rounded-full"
|
|
317
|
+
style={{
|
|
318
|
+
backgroundColor:
|
|
319
|
+
rankingColors[index % rankingColors.length],
|
|
320
|
+
}}
|
|
321
|
+
/>
|
|
322
|
+
<span className="truncate text-foreground/90">
|
|
323
|
+
#{index + 1} {item.customer}
|
|
324
|
+
</span>
|
|
325
|
+
</div>
|
|
326
|
+
))}
|
|
327
|
+
</div>
|
|
218
328
|
</CardContent>
|
|
219
329
|
</Card>
|
|
220
330
|
|
|
221
331
|
<Card className="xl:col-span-2">
|
|
222
|
-
<CardHeader>
|
|
332
|
+
<CardHeader className="pb-3">
|
|
223
333
|
<CardTitle>{t('pie.title')}</CardTitle>
|
|
224
334
|
<CardDescription>{t('pie.description')}</CardDescription>
|
|
225
335
|
</CardHeader>
|
|
226
|
-
<CardContent>
|
|
227
|
-
<ResponsiveContainer width="100%" height={
|
|
336
|
+
<CardContent className="pb-4 pt-0">
|
|
337
|
+
<ResponsiveContainer width="100%" height={340}>
|
|
228
338
|
<PieChart>
|
|
229
339
|
<Pie
|
|
230
|
-
data={
|
|
340
|
+
data={viewData.pieData}
|
|
231
341
|
dataKey="value"
|
|
232
342
|
nameKey="customer"
|
|
233
|
-
innerRadius={
|
|
234
|
-
outerRadius={
|
|
343
|
+
innerRadius={55}
|
|
344
|
+
outerRadius={100}
|
|
235
345
|
paddingAngle={2}
|
|
346
|
+
cy="42%"
|
|
236
347
|
>
|
|
237
|
-
{
|
|
348
|
+
{viewData.pieData.map((entry, index) => (
|
|
238
349
|
<Cell
|
|
239
350
|
key={entry.customer}
|
|
240
|
-
fill={
|
|
351
|
+
fill={rankingColors[index % rankingColors.length]}
|
|
352
|
+
stroke="hsl(var(--background))"
|
|
353
|
+
strokeWidth={1}
|
|
241
354
|
/>
|
|
242
355
|
))}
|
|
243
356
|
</Pie>
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
357
|
+
<Legend
|
|
358
|
+
iconType="circle"
|
|
359
|
+
verticalAlign="bottom"
|
|
360
|
+
wrapperStyle={{
|
|
361
|
+
fontSize: 12,
|
|
362
|
+
lineHeight: '20px',
|
|
363
|
+
paddingTop: 12,
|
|
364
|
+
}}
|
|
251
365
|
/>
|
|
366
|
+
<Tooltip cursor={false} content={renderSolidTooltip} />
|
|
252
367
|
</PieChart>
|
|
253
368
|
</ResponsiveContainer>
|
|
254
369
|
</CardContent>
|
|
@@ -274,7 +389,7 @@ export default function TopCustomersReportPage() {
|
|
|
274
389
|
</TableRow>
|
|
275
390
|
</TableHeader>
|
|
276
391
|
<TableBody>
|
|
277
|
-
{
|
|
392
|
+
{viewData.topCustomers.length === 0 ? (
|
|
278
393
|
<TableRow>
|
|
279
394
|
<TableCell
|
|
280
395
|
colSpan={3}
|
|
@@ -284,9 +399,11 @@ export default function TopCustomersReportPage() {
|
|
|
284
399
|
</TableCell>
|
|
285
400
|
</TableRow>
|
|
286
401
|
) : (
|
|
287
|
-
|
|
402
|
+
viewData.topCustomers.map((item) => {
|
|
288
403
|
const participation =
|
|
289
|
-
|
|
404
|
+
viewData.total > 0
|
|
405
|
+
? (item.value / viewData.total) * 100
|
|
406
|
+
: 0;
|
|
290
407
|
|
|
291
408
|
return (
|
|
292
409
|
<TableRow key={item.customer}>
|