@hed-hog/finance 0.0.299 → 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.
Files changed (79) hide show
  1. package/dist/dto/create-bank-account.dto.d.ts +1 -0
  2. package/dist/dto/create-bank-account.dto.d.ts.map +1 -1
  3. package/dist/dto/create-bank-account.dto.js +7 -0
  4. package/dist/dto/create-bank-account.dto.js.map +1 -1
  5. package/dist/dto/update-bank-account.dto.d.ts +1 -0
  6. package/dist/dto/update-bank-account.dto.d.ts.map +1 -1
  7. package/dist/dto/update-bank-account.dto.js +7 -0
  8. package/dist/dto/update-bank-account.dto.js.map +1 -1
  9. package/dist/finance-bank-accounts.controller.d.ts +3 -0
  10. package/dist/finance-bank-accounts.controller.d.ts.map +1 -1
  11. package/dist/finance-data.controller.d.ts +1 -0
  12. package/dist/finance-data.controller.d.ts.map +1 -1
  13. package/dist/finance.contract-activated.subscriber.d.ts +24 -0
  14. package/dist/finance.contract-activated.subscriber.d.ts.map +1 -0
  15. package/dist/finance.contract-activated.subscriber.js +519 -0
  16. package/dist/finance.contract-activated.subscriber.js.map +1 -0
  17. package/dist/finance.contract-activated.subscriber.spec.d.ts +2 -0
  18. package/dist/finance.contract-activated.subscriber.spec.d.ts.map +1 -0
  19. package/dist/finance.contract-activated.subscriber.spec.js +302 -0
  20. package/dist/finance.contract-activated.subscriber.spec.js.map +1 -0
  21. package/dist/finance.module.d.ts.map +1 -1
  22. package/dist/finance.module.js +6 -1
  23. package/dist/finance.module.js.map +1 -1
  24. package/dist/finance.service.d.ts +4 -0
  25. package/dist/finance.service.d.ts.map +1 -1
  26. package/dist/finance.service.js +5 -0
  27. package/dist/finance.service.js.map +1 -1
  28. package/hedhog/data/dashboard.yaml +6 -0
  29. package/hedhog/data/dashboard_component.yaml +72 -17
  30. package/hedhog/data/dashboard_component_role.yaml +30 -0
  31. package/hedhog/data/dashboard_item.yaml +155 -0
  32. package/hedhog/data/dashboard_role.yaml +6 -0
  33. package/hedhog/data/role_menu.yaml +6 -0
  34. package/hedhog/data/role_route.yaml +127 -0
  35. package/hedhog/frontend/app/_components/finance-layout.tsx.ejs +108 -0
  36. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +91 -106
  37. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1306 -1145
  38. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +288 -268
  39. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +491 -351
  40. package/hedhog/frontend/app/administration/audit-logs/page.tsx.ejs +157 -173
  41. package/hedhog/frontend/app/administration/categories/page.tsx.ejs +44 -62
  42. package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +62 -80
  43. package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +151 -170
  44. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +586 -224
  45. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +204 -226
  46. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +122 -140
  47. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +32 -49
  48. package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +84 -108
  49. package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +53 -70
  50. package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +98 -95
  51. package/hedhog/frontend/app/reports/actual-vs-forecast/page.tsx.ejs +100 -125
  52. package/hedhog/frontend/app/reports/aging-default/page.tsx.ejs +77 -105
  53. package/hedhog/frontend/app/reports/cash-position/page.tsx.ejs +99 -134
  54. package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +147 -182
  55. package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +49 -61
  56. package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +49 -67
  57. package/hedhog/frontend/messages/en.json +224 -70
  58. package/hedhog/frontend/messages/pt.json +224 -70
  59. package/hedhog/frontend/widgets/alerts.tsx.ejs +1 -1
  60. package/hedhog/frontend/widgets/bank-reconciliation-status.tsx.ejs +142 -0
  61. package/hedhog/frontend/widgets/cash-balance-kpi.tsx.ejs +9 -9
  62. package/hedhog/frontend/widgets/cash-flow-chart.tsx.ejs +1 -1
  63. package/hedhog/frontend/widgets/default-kpi.tsx.ejs +9 -9
  64. package/hedhog/frontend/widgets/payable-30d-kpi.tsx.ejs +9 -9
  65. package/hedhog/frontend/widgets/pending-approvals-kpi.tsx.ejs +78 -0
  66. package/hedhog/frontend/widgets/pending-approvals-list.tsx.ejs +147 -0
  67. package/hedhog/frontend/widgets/pending-reconciliation-kpi.tsx.ejs +84 -0
  68. package/hedhog/frontend/widgets/receivable-30d-kpi.tsx.ejs +9 -9
  69. package/hedhog/frontend/widgets/receivable-aging-analysis.tsx.ejs +163 -0
  70. package/hedhog/frontend/widgets/upcoming-payable.tsx.ejs +1 -1
  71. package/hedhog/frontend/widgets/upcoming-receivable.tsx.ejs +1 -1
  72. package/hedhog/table/bank_account.yaml +8 -0
  73. package/package.json +7 -6
  74. package/src/dto/create-bank-account.dto.ts +7 -1
  75. package/src/dto/update-bank-account.dto.ts +7 -1
  76. package/src/finance.contract-activated.subscriber.spec.ts +392 -0
  77. package/src/finance.contract-activated.subscriber.ts +780 -0
  78. package/src/finance.module.ts +6 -1
  79. package/src/finance.service.ts +4 -0
@@ -1,14 +1,9 @@
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
- import {
6
- Card,
7
- CardContent,
8
- CardDescription,
9
- CardHeader,
10
- CardTitle,
11
- } from '@/components/ui/card';
6
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
12
7
  import { Input } from '@/components/ui/input';
13
8
  import { Label } from '@/components/ui/label';
14
9
  import { Money } from '@/components/ui/money';
@@ -108,6 +103,56 @@ export default function OverviewResultsReportPage() {
108
103
  [data.rows, groupBy, locale]
109
104
  );
110
105
  const totals = data.totals;
106
+ const summaryCards = [
107
+ {
108
+ key: 'revenue',
109
+ title: t('cards.revenue'),
110
+ value: <Money value={totals.faturamento} />,
111
+ icon: TrendingUp,
112
+ layout: 'compact' as const,
113
+ iconContainerClassName: 'bg-blue-500/10 text-blue-600',
114
+ accentClassName: 'from-blue-500/20 via-sky-500/10 to-transparent',
115
+ },
116
+ {
117
+ key: 'expensesAndLoans',
118
+ title: t('cards.expensesAndLoans'),
119
+ value: <Money value={totals.despesasEmprestimos} />,
120
+ icon: TrendingDown,
121
+ layout: 'compact' as const,
122
+ iconContainerClassName: 'bg-orange-500/10 text-orange-600',
123
+ accentClassName: 'from-orange-500/20 via-amber-500/10 to-transparent',
124
+ },
125
+ {
126
+ key: 'difference',
127
+ title: t('cards.resultDifference'),
128
+ value: <Money value={totals.diferenca} showSign />,
129
+ icon: totals.diferenca >= 0 ? TrendingUp : TrendingDown,
130
+ layout: 'compact' as const,
131
+ valueClassName: totals.diferenca >= 0 ? 'text-green-600' : 'text-red-600',
132
+ iconContainerClassName:
133
+ totals.diferenca >= 0
134
+ ? 'bg-green-500/10 text-green-600'
135
+ : 'bg-red-500/10 text-red-600',
136
+ accentClassName:
137
+ totals.diferenca >= 0
138
+ ? 'from-green-500/20 via-emerald-500/10 to-transparent'
139
+ : 'from-red-500/20 via-rose-500/10 to-transparent',
140
+ },
141
+ {
142
+ key: 'margin',
143
+ title: t('cards.margin'),
144
+ value: `${totals.margem.toFixed(1)}%`,
145
+ description: (
146
+ <Badge variant={totals.margem >= 0 ? 'default' : 'destructive'}>
147
+ {totals.margem >= 0 ? t('status.positive') : t('status.negative')}
148
+ </Badge>
149
+ ),
150
+ icon: Target,
151
+ layout: 'compact' as const,
152
+ iconContainerClassName: 'bg-violet-500/10 text-violet-600',
153
+ accentClassName: 'from-violet-500/20 via-fuchsia-500/10 to-transparent',
154
+ },
155
+ ];
111
156
 
112
157
  return (
113
158
  <Page>
@@ -121,182 +166,102 @@ export default function OverviewResultsReportPage() {
121
166
  ]}
122
167
  />
123
168
 
124
- <Card>
125
- <CardHeader>
126
- <CardTitle>{t('filters.title')}</CardTitle>
127
- <CardDescription>{t('filters.description')}</CardDescription>
128
- </CardHeader>
129
- <CardContent className="grid gap-4 md:grid-cols-3">
130
- <div className="space-y-2">
131
- <Label htmlFor="from">{t('filters.from')}</Label>
132
- <Input
133
- id="from"
134
- type="date"
135
- value={from}
136
- onChange={(event) => setFrom(event.target.value)}
137
- max={to}
138
- />
139
- </div>
140
- <div className="space-y-2">
141
- <Label htmlFor="to">{t('filters.to')}</Label>
142
- <Input
143
- id="to"
144
- type="date"
145
- value={to}
146
- onChange={(event) => setTo(event.target.value)}
147
- min={from}
148
- />
149
- </div>
150
- <div className="space-y-2">
151
- <Label>{t('filters.groupBy')}</Label>
152
- <Select
153
- value={groupBy}
154
- onValueChange={(value) => setGroupBy(value as GroupBy)}
155
- >
156
- <SelectTrigger>
157
- <SelectValue />
158
- </SelectTrigger>
159
- <SelectContent>
160
- <SelectItem value="day">{t('groupBy.day')}</SelectItem>
161
- <SelectItem value="week">{t('groupBy.week')}</SelectItem>
162
- <SelectItem value="month">{t('groupBy.month')}</SelectItem>
163
- <SelectItem value="year">{t('groupBy.year')}</SelectItem>
164
- </SelectContent>
165
- </Select>
166
- </div>
167
- </CardContent>
168
- </Card>
169
-
170
- <div className="grid gap-3 md:grid-cols-4">
171
- <Card>
172
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
173
- <CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
174
- {t('cards.revenue')}
175
- </CardTitle>
176
- <div className="rounded-full bg-blue-500/10 p-1.5">
177
- <TrendingUp className="h-4 w-4 text-blue-600" />
178
- </div>
179
- </CardHeader>
180
- <CardContent className="pb-3 pt-0 px-4">
181
- <div className="text-xl font-bold">
182
- <Money value={totals.faturamento} />
183
- </div>
184
- </CardContent>
185
- </Card>
186
-
187
- <Card>
188
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
189
- <CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
190
- {t('cards.expensesAndLoans')}
191
- </CardTitle>
192
- <div className="rounded-full bg-orange-500/10 p-1.5">
193
- <TrendingDown className="h-4 w-4 text-orange-600" />
194
- </div>
195
- </CardHeader>
196
- <CardContent className="pb-3 pt-0 px-4">
197
- <div className="text-xl font-bold">
198
- <Money value={totals.despesasEmprestimos} />
199
- </div>
200
- </CardContent>
201
- </Card>
202
-
203
- <Card>
204
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
205
- <CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
206
- {t('cards.resultDifference')}
207
- </CardTitle>
208
- <div
209
- className={`rounded-full p-1.5 ${totals.diferenca >= 0 ? 'bg-green-500/10' : 'bg-red-500/10'}`}
210
- >
211
- {totals.diferenca >= 0 ? (
212
- <TrendingUp className="h-4 w-4 text-green-600" />
213
- ) : (
214
- <TrendingDown className="h-4 w-4 text-red-600" />
215
- )}
216
- </div>
217
- </CardHeader>
218
- <CardContent className="pb-3 pt-0 px-4">
219
- <div
220
- className={`text-xl font-bold ${totals.diferenca >= 0 ? 'text-green-600' : 'text-red-600'}`}
221
- >
222
- <Money value={totals.diferenca} showSign />
223
- </div>
224
- </CardContent>
225
- </Card>
169
+ <FinancePageSection
170
+ title={t('filters.title')}
171
+ description={t('filters.description')}
172
+ contentClassName="grid gap-4 p-4 md:grid-cols-3 sm:p-5"
173
+ >
174
+ <div className="space-y-2">
175
+ <Label htmlFor="from">{t('filters.from')}</Label>
176
+ <Input
177
+ id="from"
178
+ type="date"
179
+ value={from}
180
+ onChange={(event) => setFrom(event.target.value)}
181
+ max={to}
182
+ />
183
+ </div>
184
+ <div className="space-y-2">
185
+ <Label htmlFor="to">{t('filters.to')}</Label>
186
+ <Input
187
+ id="to"
188
+ type="date"
189
+ value={to}
190
+ onChange={(event) => setTo(event.target.value)}
191
+ min={from}
192
+ />
193
+ </div>
194
+ <div className="space-y-2">
195
+ <Label>{t('filters.groupBy')}</Label>
196
+ <Select
197
+ value={groupBy}
198
+ onValueChange={(value) => setGroupBy(value as GroupBy)}
199
+ >
200
+ <SelectTrigger>
201
+ <SelectValue />
202
+ </SelectTrigger>
203
+ <SelectContent>
204
+ <SelectItem value="day">{t('groupBy.day')}</SelectItem>
205
+ <SelectItem value="week">{t('groupBy.week')}</SelectItem>
206
+ <SelectItem value="month">{t('groupBy.month')}</SelectItem>
207
+ <SelectItem value="year">{t('groupBy.year')}</SelectItem>
208
+ </SelectContent>
209
+ </Select>
210
+ </div>
211
+ </FinancePageSection>
226
212
 
227
- <Card>
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">
230
- {t('cards.margin')}
231
- </CardTitle>
232
- <div className="rounded-full bg-violet-500/10 p-1.5">
233
- <Target className="h-4 w-4 text-violet-600" />
234
- </div>
235
- </CardHeader>
236
- <CardContent className="pb-3 pt-0 px-4">
237
- <div className="text-xl font-bold">{totals.margem.toFixed(1)}%</div>
238
- <Badge
239
- className="mt-1"
240
- variant={totals.margem >= 0 ? 'default' : 'destructive'}
241
- >
242
- {totals.margem >= 0 ? t('status.positive') : t('status.negative')}
243
- </Badge>
244
- </CardContent>
245
- </Card>
246
- </div>
213
+ <KpiCardsGrid items={summaryCards} columns={4} />
247
214
 
248
- <Card>
249
- <CardHeader>
250
- <CardTitle>{t('chart.title')}</CardTitle>
251
- <CardDescription>{t('chart.description')}</CardDescription>
252
- </CardHeader>
253
- <CardContent>
254
- <ResponsiveContainer width="100%" height={360}>
255
- <LineChart data={rows}>
256
- <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
257
- <XAxis dataKey="label" tick={{ fontSize: 12 }} />
258
- <YAxis
259
- tick={{ fontSize: 12 }}
260
- tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
261
- />
262
- <Tooltip content={renderSolidTooltip} />
263
- <Legend />
264
- <Line
265
- type="monotone"
266
- dataKey="faturamento"
267
- name={t('chart.revenueLabel')}
268
- stroke={LINE_COLORS.faturamento}
269
- strokeWidth={3}
270
- dot={false}
271
- />
272
- <Line
273
- type="monotone"
274
- dataKey="despesasEmprestimos"
275
- name={t('chart.expensesLabel')}
276
- stroke={LINE_COLORS.despesasEmprestimos}
277
- strokeWidth={3}
278
- dot={false}
279
- />
280
- <Line
281
- type="monotone"
282
- dataKey="diferenca"
283
- name={t('chart.differenceLabel')}
284
- stroke={LINE_COLORS.diferenca}
285
- strokeWidth={2}
286
- strokeDasharray="6 4"
287
- dot={false}
288
- />
289
- </LineChart>
290
- </ResponsiveContainer>
291
- </CardContent>
292
- </Card>
215
+ <FinancePageSection
216
+ title={t('chart.title')}
217
+ description={t('chart.description')}
218
+ contentClassName="p-4 sm:p-5"
219
+ >
220
+ <ResponsiveContainer width="100%" height={360}>
221
+ <LineChart data={rows}>
222
+ <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
223
+ <XAxis dataKey="label" tick={{ fontSize: 12 }} />
224
+ <YAxis
225
+ tick={{ fontSize: 12 }}
226
+ tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
227
+ />
228
+ <Tooltip content={renderSolidTooltip} />
229
+ <Legend />
230
+ <Line
231
+ type="monotone"
232
+ dataKey="faturamento"
233
+ name={t('chart.revenueLabel')}
234
+ stroke={LINE_COLORS.faturamento}
235
+ strokeWidth={3}
236
+ dot={false}
237
+ />
238
+ <Line
239
+ type="monotone"
240
+ dataKey="despesasEmprestimos"
241
+ name={t('chart.expensesLabel')}
242
+ stroke={LINE_COLORS.despesasEmprestimos}
243
+ strokeWidth={3}
244
+ dot={false}
245
+ />
246
+ <Line
247
+ type="monotone"
248
+ dataKey="diferenca"
249
+ name={t('chart.differenceLabel')}
250
+ stroke={LINE_COLORS.diferenca}
251
+ strokeWidth={2}
252
+ strokeDasharray="6 4"
253
+ dot={false}
254
+ />
255
+ </LineChart>
256
+ </ResponsiveContainer>
257
+ </FinancePageSection>
293
258
 
294
- <Card>
295
- <CardHeader>
296
- <CardTitle>{t('table.title')}</CardTitle>
297
- <CardDescription>{t('table.description')}</CardDescription>
298
- </CardHeader>
299
- <CardContent>
259
+ <FinancePageSection
260
+ title={t('table.title')}
261
+ description={t('table.description')}
262
+ contentClassName="p-4 sm:p-5"
263
+ >
264
+ <div className="overflow-x-auto">
300
265
  <Table>
301
266
  <TableHeader>
302
267
  <TableRow>
@@ -348,8 +313,8 @@ export default function OverviewResultsReportPage() {
348
313
  )}
349
314
  </TableBody>
350
315
  </Table>
351
- </CardContent>
352
- </Card>
316
+ </div>
317
+ </FinancePageSection>
353
318
  </Page>
354
319
  );
355
320
  }
@@ -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 { Button } from '@/components/ui/button';
5
6
  import {
@@ -10,6 +11,7 @@ import {
10
11
  CardTitle,
11
12
  } from '@/components/ui/card';
12
13
  import { Input } from '@/components/ui/input';
14
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
13
15
  import { Money } from '@/components/ui/money';
14
16
  import {
15
17
  Select,
@@ -90,6 +92,41 @@ export default function TopCustomersReportPage() {
90
92
  setAppliedFilters(filters);
91
93
  };
92
94
 
95
+ const summaryCards = [
96
+ {
97
+ key: 'total',
98
+ title: t('cards.total'),
99
+ value: <Money value={viewData.total} />,
100
+ icon: Users,
101
+ layout: 'compact' as const,
102
+ iconContainerClassName: 'bg-blue-500/10 text-blue-600',
103
+ accentClassName: 'from-blue-500/20 via-sky-500/10 to-transparent',
104
+ },
105
+ {
106
+ key: 'top5Concentration',
107
+ title: t('cards.top5Concentration'),
108
+ value: `${viewData.top5Percent.toFixed(1)}%`,
109
+ icon: PieChartIcon,
110
+ layout: 'compact' as const,
111
+ iconContainerClassName: 'bg-purple-500/10 text-purple-600',
112
+ accentClassName: 'from-purple-500/20 via-fuchsia-500/10 to-transparent',
113
+ },
114
+ {
115
+ key: 'leader',
116
+ title: t('cards.leader'),
117
+ value: viewData.leader?.customer || '-',
118
+ description: viewData.leader ? (
119
+ <Money value={viewData.leader.value} />
120
+ ) : (
121
+ '-'
122
+ ),
123
+ icon: Crown,
124
+ layout: 'compact' as const,
125
+ iconContainerClassName: 'bg-amber-500/10 text-amber-500',
126
+ accentClassName: 'from-amber-500/20 via-yellow-500/10 to-transparent',
127
+ },
128
+ ];
129
+
93
130
  const renderSolidTooltip = ({
94
131
  active,
95
132
  payload,
@@ -165,7 +202,7 @@ export default function TopCustomersReportPage() {
165
202
  }))
166
203
  }
167
204
  max={filters.to}
168
- className="w-full lg:w-[170px]"
205
+ className="w-full lg:w-42.5"
169
206
  aria-label={t('filters.fromAria')}
170
207
  placeholder={t('filters.fromPlaceholder')}
171
208
  />
@@ -180,7 +217,7 @@ export default function TopCustomersReportPage() {
180
217
  }))
181
218
  }
182
219
  min={filters.from}
183
- className="w-full lg:w-[170px]"
220
+ className="w-full lg:w-42.5"
184
221
  aria-label={t('filters.toAria')}
185
222
  placeholder={t('filters.toPlaceholder')}
186
223
  />
@@ -194,7 +231,7 @@ export default function TopCustomersReportPage() {
194
231
  }
195
232
  >
196
233
  <SelectTrigger
197
- className="w-full lg:w-[150px]"
234
+ className="w-full lg:w-37.5"
198
235
  aria-label={t('filters.groupByAria')}
199
236
  >
200
237
  <SelectValue placeholder={t('filters.groupByPlaceholder')} />
@@ -215,56 +252,7 @@ export default function TopCustomersReportPage() {
215
252
  </Button>
216
253
  </form>
217
254
 
218
- <div className="grid gap-3 md:grid-cols-3">
219
- <Card>
220
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
221
- <CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
222
- {t('cards.total')}
223
- </CardTitle>
224
- <div className="rounded-full bg-blue-500/10 p-1.5">
225
- <Users className="h-4 w-4 text-blue-600" />
226
- </div>
227
- </CardHeader>
228
- <CardContent className="pb-3 pt-0 px-4">
229
- <div className="text-xl font-bold">
230
- <Money value={viewData.total} />
231
- </div>
232
- </CardContent>
233
- </Card>
234
- <Card>
235
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
236
- <CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
237
- {t('cards.top5Concentration')}
238
- </CardTitle>
239
- <div className="rounded-full bg-purple-500/10 p-1.5">
240
- <PieChartIcon className="h-4 w-4 text-purple-600" />
241
- </div>
242
- </CardHeader>
243
- <CardContent className="pb-3 pt-0 px-4">
244
- <div className="text-xl font-bold">
245
- {viewData.top5Percent.toFixed(1)}%
246
- </div>
247
- </CardContent>
248
- </Card>
249
- <Card>
250
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pt-3 px-4">
251
- <CardTitle className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
252
- {t('cards.leader')}
253
- </CardTitle>
254
- <div className="rounded-full bg-amber-500/10 p-1.5">
255
- <Crown className="h-4 w-4 text-amber-500" />
256
- </div>
257
- </CardHeader>
258
- <CardContent className="pb-3 pt-0 px-4">
259
- <div className="text-sm font-medium">
260
- {viewData.leader?.customer || '-'}
261
- </div>
262
- <div className="text-xs text-muted-foreground">
263
- {viewData.leader ? <Money value={viewData.leader.value} /> : '-'}
264
- </div>
265
- </CardContent>
266
- </Card>
267
- </div>
255
+ <KpiCardsGrid items={summaryCards} columns={3} />
268
256
 
269
257
  <div className="grid gap-3 xl:grid-cols-5">
270
258
  <Card className="xl:col-span-3">
@@ -362,12 +350,12 @@ export default function TopCustomersReportPage() {
362
350
  </Card>
363
351
  </div>
364
352
 
365
- <Card>
366
- <CardHeader>
367
- <CardTitle>{t('table.title')}</CardTitle>
368
- <CardDescription>{t('table.description')}</CardDescription>
369
- </CardHeader>
370
- <CardContent>
353
+ <FinancePageSection
354
+ title={t('table.title')}
355
+ description={t('table.description')}
356
+ contentClassName="p-4 sm:p-5"
357
+ >
358
+ <div className="overflow-x-auto">
371
359
  <Table>
372
360
  <TableHeader>
373
361
  <TableRow>
@@ -412,8 +400,8 @@ export default function TopCustomersReportPage() {
412
400
  )}
413
401
  </TableBody>
414
402
  </Table>
415
- </CardContent>
416
- </Card>
403
+ </div>
404
+ </FinancePageSection>
417
405
  </Page>
418
406
  );
419
407
  }