@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.
Files changed (39) hide show
  1. package/README.md +65 -29
  2. package/dist/dto/finance-report-query.dto.d.ts +16 -0
  3. package/dist/dto/finance-report-query.dto.d.ts.map +1 -0
  4. package/dist/dto/finance-report-query.dto.js +59 -0
  5. package/dist/dto/finance-report-query.dto.js.map +1 -0
  6. package/dist/finance-reports.controller.d.ts +71 -0
  7. package/dist/finance-reports.controller.d.ts.map +1 -0
  8. package/dist/finance-reports.controller.js +61 -0
  9. package/dist/finance-reports.controller.js.map +1 -0
  10. package/dist/finance.module.d.ts.map +1 -1
  11. package/dist/finance.module.js +2 -0
  12. package/dist/finance.module.js.map +1 -1
  13. package/dist/finance.service.d.ts +93 -0
  14. package/dist/finance.service.d.ts.map +1 -1
  15. package/dist/finance.service.js +456 -0
  16. package/dist/finance.service.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/hedhog/data/menu.yaml +46 -0
  22. package/hedhog/data/route.yaml +27 -0
  23. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +158 -125
  24. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +102 -88
  25. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +113 -89
  26. package/hedhog/frontend/app/reports/_lib/report-aggregations.ts.ejs +275 -0
  27. package/hedhog/frontend/app/reports/_lib/report-mocks.ts.ejs +186 -0
  28. package/hedhog/frontend/app/reports/_lib/use-finance-reports.ts.ejs +233 -0
  29. package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +355 -0
  30. package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +427 -0
  31. package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +433 -0
  32. package/hedhog/frontend/messages/en.json +179 -0
  33. package/hedhog/frontend/messages/pt.json +179 -0
  34. package/package.json +7 -7
  35. package/src/dto/finance-report-query.dto.ts +49 -0
  36. package/src/finance-reports.controller.ts +28 -0
  37. package/src/finance.module.ts +2 -0
  38. package/src/finance.service.ts +645 -10
  39. package/src/index.ts +1 -0
@@ -0,0 +1,355 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Badge } from '@/components/ui/badge';
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 { Label } from '@/components/ui/label';
14
+ import { Money } from '@/components/ui/money';
15
+ import {
16
+ Select,
17
+ SelectContent,
18
+ SelectItem,
19
+ SelectTrigger,
20
+ SelectValue,
21
+ } from '@/components/ui/select';
22
+ import {
23
+ Table,
24
+ TableBody,
25
+ TableCell,
26
+ TableHead,
27
+ TableHeader,
28
+ TableRow,
29
+ } from '@/components/ui/table';
30
+ import { Target, TrendingDown, TrendingUp } from 'lucide-react';
31
+ import { useLocale, useTranslations } from 'next-intl';
32
+ import { useMemo, useState } from 'react';
33
+ import type { TooltipProps } from 'recharts';
34
+ import {
35
+ CartesianGrid,
36
+ Legend,
37
+ Line,
38
+ LineChart,
39
+ ResponsiveContainer,
40
+ Tooltip,
41
+ XAxis,
42
+ YAxis,
43
+ } from 'recharts';
44
+ import {
45
+ formatReportBucketLabel,
46
+ getDefaultDateRange,
47
+ type GroupBy,
48
+ useOverviewResultsReport,
49
+ } from '../_lib/use-finance-reports';
50
+
51
+ const LINE_COLORS = {
52
+ faturamento: '#2563eb',
53
+ despesasEmprestimos: '#ea580c',
54
+ diferenca: '#16a34a',
55
+ };
56
+
57
+ const currencyFormatter = new Intl.NumberFormat('pt-BR', {
58
+ style: 'currency',
59
+ currency: 'BRL',
60
+ });
61
+
62
+ function renderSolidTooltip({
63
+ active,
64
+ payload,
65
+ label,
66
+ }: TooltipProps<number, string>) {
67
+ if (!active || !payload?.length) return null;
68
+ return (
69
+ <div className="rounded-lg border bg-popover px-3 py-2 shadow-xl text-popover-foreground text-sm">
70
+ <p className="mb-1 font-semibold text-popover-foreground">{label}</p>
71
+ {payload.map((entry) => (
72
+ <div key={entry.dataKey} className="flex items-center gap-2 py-0.5">
73
+ <span
74
+ className="inline-block h-2.5 w-2.5 rounded-sm shrink-0"
75
+ style={{ background: entry.color }}
76
+ />
77
+ <span className="text-muted-foreground">{entry.name}:</span>
78
+ <span className="font-semibold text-popover-foreground">
79
+ {typeof entry.value === 'number'
80
+ ? currencyFormatter.format(entry.value)
81
+ : entry.value}
82
+ </span>
83
+ </div>
84
+ ))}
85
+ </div>
86
+ );
87
+ }
88
+
89
+ export default function OverviewResultsReportPage() {
90
+ const t = useTranslations('finance.OverviewResultsReportPage');
91
+ const locale = useLocale();
92
+ const defaults = getDefaultDateRange();
93
+ const [from, setFrom] = useState(defaults.from);
94
+ const [to, setTo] = useState(defaults.to);
95
+ const [groupBy, setGroupBy] = useState<GroupBy>('year');
96
+ const { data } = useOverviewResultsReport({
97
+ from,
98
+ to,
99
+ groupBy,
100
+ });
101
+
102
+ const rows = useMemo(
103
+ () =>
104
+ data.rows.map((row) => ({
105
+ ...row,
106
+ label: formatReportBucketLabel(row.period, groupBy, locale),
107
+ })),
108
+ [data.rows, groupBy, locale]
109
+ );
110
+ const totals = data.totals;
111
+
112
+ return (
113
+ <Page>
114
+ <PageHeader
115
+ title={t('header.title')}
116
+ description={t('header.description')}
117
+ breadcrumbs={[
118
+ { label: t('breadcrumbs.home'), href: '/' },
119
+ { label: t('breadcrumbs.finance'), href: '/finance' },
120
+ { label: t('breadcrumbs.current') },
121
+ ]}
122
+ />
123
+
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>
226
+
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>
247
+
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>
293
+
294
+ <Card>
295
+ <CardHeader>
296
+ <CardTitle>{t('table.title')}</CardTitle>
297
+ <CardDescription>{t('table.description')}</CardDescription>
298
+ </CardHeader>
299
+ <CardContent>
300
+ <Table>
301
+ <TableHeader>
302
+ <TableRow>
303
+ <TableHead>{t('table.headers.period')}</TableHead>
304
+ <TableHead className="text-right">
305
+ {t('table.headers.revenue')}
306
+ </TableHead>
307
+ <TableHead className="text-right">
308
+ {t('table.headers.expensesAndLoans')}
309
+ </TableHead>
310
+ <TableHead className="text-right">
311
+ {t('table.headers.investorContribution')}
312
+ </TableHead>
313
+ <TableHead className="text-right">
314
+ {t('table.headers.difference')}
315
+ </TableHead>
316
+ </TableRow>
317
+ </TableHeader>
318
+ <TableBody>
319
+ {rows.length === 0 ? (
320
+ <TableRow>
321
+ <TableCell
322
+ colSpan={5}
323
+ className="text-muted-foreground text-center"
324
+ >
325
+ {t('table.empty')}
326
+ </TableCell>
327
+ </TableRow>
328
+ ) : (
329
+ rows.map((row) => (
330
+ <TableRow key={row.period}>
331
+ <TableCell>{row.label}</TableCell>
332
+ <TableCell className="text-right">
333
+ <Money value={row.faturamento} />
334
+ </TableCell>
335
+ <TableCell className="text-right">
336
+ <Money value={row.despesasEmprestimos} />
337
+ </TableCell>
338
+ <TableCell className="text-right">
339
+ <Money value={row.aporteInvestidor} />
340
+ </TableCell>
341
+ <TableCell
342
+ className={`text-right font-semibold ${row.diferenca >= 0 ? 'text-green-600' : 'text-red-600'}`}
343
+ >
344
+ <Money value={row.diferenca} showSign />
345
+ </TableCell>
346
+ </TableRow>
347
+ ))
348
+ )}
349
+ </TableBody>
350
+ </Table>
351
+ </CardContent>
352
+ </Card>
353
+ </Page>
354
+ );
355
+ }