@hed-hog/finance 0.0.279 → 0.0.286
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 +238 -0
- package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +96 -78
- package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +239 -130
- package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +242 -135
- package/hedhog/frontend/messages/en.json +33 -2
- package/hedhog/frontend/messages/pt.json +33 -2
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Page, PageHeader } from '@/components/entity-list';
|
|
3
|
+
import { EmptyState, Page, PageHeader } from '@/components/entity-list';
|
|
4
4
|
import { Button } from '@/components/ui/button';
|
|
5
5
|
import {
|
|
6
6
|
Card,
|
|
@@ -357,16 +357,27 @@ function ImportarExtratoSheet({
|
|
|
357
357
|
defaultBankAccountId,
|
|
358
358
|
onImported,
|
|
359
359
|
onBankAccountCreated,
|
|
360
|
+
open,
|
|
361
|
+
onOpenChange,
|
|
360
362
|
}: {
|
|
361
363
|
contasBancarias: BankAccount[];
|
|
362
364
|
t: ReturnType<typeof useTranslations>;
|
|
363
365
|
defaultBankAccountId?: string;
|
|
364
366
|
onImported: () => Promise<any> | void;
|
|
365
367
|
onBankAccountCreated: (createdBankAccountId?: string) => Promise<void> | void;
|
|
368
|
+
open?: boolean;
|
|
369
|
+
onOpenChange?: (open: boolean) => void;
|
|
366
370
|
}) {
|
|
367
371
|
const { request, showToastHandler } = useApp();
|
|
368
|
-
const [
|
|
372
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
369
373
|
const [openNovaContaSheet, setOpenNovaContaSheet] = useState(false);
|
|
374
|
+
const isOpen = open ?? internalOpen;
|
|
375
|
+
const handleOpenChange = (nextOpen: boolean) => {
|
|
376
|
+
onOpenChange?.(nextOpen);
|
|
377
|
+
if (open === undefined) {
|
|
378
|
+
setInternalOpen(nextOpen);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
370
381
|
|
|
371
382
|
const form = useForm<ImportStatementFormValues>({
|
|
372
383
|
resolver: zodResolver(importStatementSchema),
|
|
@@ -376,7 +387,7 @@ function ImportarExtratoSheet({
|
|
|
376
387
|
});
|
|
377
388
|
|
|
378
389
|
useEffect(() => {
|
|
379
|
-
if (!
|
|
390
|
+
if (!isOpen) {
|
|
380
391
|
return;
|
|
381
392
|
}
|
|
382
393
|
|
|
@@ -384,7 +395,7 @@ function ImportarExtratoSheet({
|
|
|
384
395
|
bankAccountId: defaultBankAccountId || '',
|
|
385
396
|
file: undefined as unknown as File,
|
|
386
397
|
});
|
|
387
|
-
}, [defaultBankAccountId, form,
|
|
398
|
+
}, [defaultBankAccountId, form, isOpen]);
|
|
388
399
|
|
|
389
400
|
const handleSubmit = async (values: ImportStatementFormValues) => {
|
|
390
401
|
const formData = new FormData();
|
|
@@ -400,15 +411,15 @@ function ImportarExtratoSheet({
|
|
|
400
411
|
|
|
401
412
|
await onImported();
|
|
402
413
|
showToastHandler?.('success', 'Extrato importado com sucesso');
|
|
403
|
-
|
|
414
|
+
handleOpenChange(false);
|
|
404
415
|
} catch {
|
|
405
416
|
showToastHandler?.('error', 'Não foi possível importar o extrato');
|
|
406
417
|
}
|
|
407
418
|
};
|
|
408
419
|
|
|
409
420
|
return (
|
|
410
|
-
<Sheet open={
|
|
411
|
-
<Button onClick={() =>
|
|
421
|
+
<Sheet open={isOpen} onOpenChange={handleOpenChange}>
|
|
422
|
+
<Button onClick={() => handleOpenChange(true)}>
|
|
412
423
|
<Upload className="mr-2 h-4 w-4" />
|
|
413
424
|
{t('importDialog.action')}
|
|
414
425
|
</Button>
|
|
@@ -510,7 +521,7 @@ function ImportarExtratoSheet({
|
|
|
510
521
|
<Button
|
|
511
522
|
type="button"
|
|
512
523
|
variant="outline"
|
|
513
|
-
onClick={() =>
|
|
524
|
+
onClick={() => handleOpenChange(false)}
|
|
514
525
|
>
|
|
515
526
|
{t('common.cancel')}
|
|
516
527
|
</Button>
|
|
@@ -536,6 +547,7 @@ export default function ExtratosPage() {
|
|
|
536
547
|
const [contaFilter, setContaFilter] = useState<string>('');
|
|
537
548
|
const [search, setSearch] = useState('');
|
|
538
549
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
550
|
+
const [isImportSheetOpen, setIsImportSheetOpen] = useState(false);
|
|
539
551
|
const [extratoSelecionado, setExtratoSelecionado] =
|
|
540
552
|
useState<Statement | null>(null);
|
|
541
553
|
|
|
@@ -717,6 +729,8 @@ export default function ExtratosPage() {
|
|
|
717
729
|
contasBancarias={contasBancarias}
|
|
718
730
|
t={t}
|
|
719
731
|
defaultBankAccountId={contaFilter}
|
|
732
|
+
open={isImportSheetOpen}
|
|
733
|
+
onOpenChange={setIsImportSheetOpen}
|
|
720
734
|
onImported={refetchExtratos}
|
|
721
735
|
onBankAccountCreated={handleBankAccountCreated}
|
|
722
736
|
/>
|
|
@@ -797,89 +811,99 @@ export default function ExtratosPage() {
|
|
|
797
811
|
</Card>
|
|
798
812
|
</div>
|
|
799
813
|
|
|
800
|
-
|
|
801
|
-
<
|
|
802
|
-
<
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
<
|
|
809
|
-
<
|
|
810
|
-
<
|
|
811
|
-
<
|
|
812
|
-
<
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
event.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}}
|
|
839
|
-
role="button"
|
|
840
|
-
tabIndex={0}
|
|
841
|
-
>
|
|
842
|
-
<TableCell>{formatarData(extrato.data)}</TableCell>
|
|
843
|
-
<TableCell className="truncate" title={extrato.descricao}>
|
|
844
|
-
{extrato.descricao}
|
|
845
|
-
</TableCell>
|
|
846
|
-
<TableCell className="text-right">
|
|
847
|
-
<span
|
|
848
|
-
className={
|
|
849
|
-
extrato.tipo === 'entrada'
|
|
850
|
-
? 'text-green-600'
|
|
851
|
-
: 'text-red-600'
|
|
814
|
+
{extratos.length > 0 ? (
|
|
815
|
+
<Card>
|
|
816
|
+
<CardHeader>
|
|
817
|
+
<CardTitle>{t('table.title')}</CardTitle>
|
|
818
|
+
<CardDescription>
|
|
819
|
+
{t('table.foundTransactions', { count: extratos.length })}
|
|
820
|
+
</CardDescription>
|
|
821
|
+
</CardHeader>
|
|
822
|
+
<CardContent>
|
|
823
|
+
<div className="overflow-x-auto">
|
|
824
|
+
<Table className="min-w-[760px] table-fixed">
|
|
825
|
+
<TableHeader>
|
|
826
|
+
<TableRow>
|
|
827
|
+
<TableHead className="w-[110px]">
|
|
828
|
+
{t('table.headers.date')}
|
|
829
|
+
</TableHead>
|
|
830
|
+
<TableHead>{t('table.headers.description')}</TableHead>
|
|
831
|
+
<TableHead className="w-[130px] text-right">
|
|
832
|
+
{t('table.headers.value')}
|
|
833
|
+
</TableHead>
|
|
834
|
+
<TableHead className="w-[110px]">
|
|
835
|
+
{t('table.headers.type')}
|
|
836
|
+
</TableHead>
|
|
837
|
+
<TableHead className="w-[140px]">
|
|
838
|
+
{t('table.headers.reconciliation')}
|
|
839
|
+
</TableHead>
|
|
840
|
+
</TableRow>
|
|
841
|
+
</TableHeader>
|
|
842
|
+
<TableBody>
|
|
843
|
+
{extratos.map((extrato) => (
|
|
844
|
+
<TableRow
|
|
845
|
+
key={extrato.id}
|
|
846
|
+
className="cursor-pointer"
|
|
847
|
+
onClick={() => setExtratoSelecionado(extrato)}
|
|
848
|
+
onKeyDown={(event) => {
|
|
849
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
850
|
+
event.preventDefault();
|
|
851
|
+
setExtratoSelecionado(extrato);
|
|
852
852
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
{extrato.
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
853
|
+
}}
|
|
854
|
+
role="button"
|
|
855
|
+
tabIndex={0}
|
|
856
|
+
>
|
|
857
|
+
<TableCell>{formatarData(extrato.data)}</TableCell>
|
|
858
|
+
<TableCell className="truncate" title={extrato.descricao}>
|
|
859
|
+
{extrato.descricao}
|
|
860
|
+
</TableCell>
|
|
861
|
+
<TableCell className="text-right">
|
|
862
|
+
<span
|
|
863
|
+
className={
|
|
864
|
+
extrato.tipo === 'entrada'
|
|
865
|
+
? 'text-green-600'
|
|
866
|
+
: 'text-red-600'
|
|
867
|
+
}
|
|
868
|
+
>
|
|
869
|
+
<Money value={extrato.valor} />
|
|
867
870
|
</span>
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
871
|
+
</TableCell>
|
|
872
|
+
<TableCell>
|
|
873
|
+
{extrato.tipo === 'entrada' ? (
|
|
874
|
+
<span className="flex items-center gap-1 text-green-600">
|
|
875
|
+
<ArrowUpRight className="h-4 w-4" />
|
|
876
|
+
{t('types.inflow')}
|
|
877
|
+
</span>
|
|
878
|
+
) : (
|
|
879
|
+
<span className="flex items-center gap-1 text-red-600">
|
|
880
|
+
<ArrowDownRight className="h-4 w-4" />
|
|
881
|
+
{t('types.outflow')}
|
|
882
|
+
</span>
|
|
883
|
+
)}
|
|
884
|
+
</TableCell>
|
|
885
|
+
<TableCell>
|
|
886
|
+
<StatusBadge
|
|
887
|
+
status={extrato.statusConciliacao}
|
|
888
|
+
type="conciliacao"
|
|
889
|
+
/>
|
|
890
|
+
</TableCell>
|
|
891
|
+
</TableRow>
|
|
892
|
+
))}
|
|
893
|
+
</TableBody>
|
|
894
|
+
</Table>
|
|
895
|
+
</div>
|
|
896
|
+
</CardContent>
|
|
897
|
+
</Card>
|
|
898
|
+
) : (
|
|
899
|
+
<EmptyState
|
|
900
|
+
icon={<Upload className="h-12 w-12" />}
|
|
901
|
+
title={t('empty.title')}
|
|
902
|
+
description={t('empty.description')}
|
|
903
|
+
actionLabel={t('importDialog.action')}
|
|
904
|
+
onAction={() => setIsImportSheetOpen(true)}
|
|
905
|
+
/>
|
|
906
|
+
)}
|
|
883
907
|
|
|
884
908
|
<Dialog
|
|
885
909
|
open={!!extratoSelecionado}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
4
|
+
|
|
5
|
+
export type GroupBy = 'day' | 'week' | 'month' | 'year';
|
|
6
|
+
|
|
7
|
+
type QueryValue = string | number | undefined | null;
|
|
8
|
+
|
|
9
|
+
export type OverviewResultsRow = {
|
|
10
|
+
period: string;
|
|
11
|
+
faturamento: number;
|
|
12
|
+
despesasEmprestimos: number;
|
|
13
|
+
diferenca: number;
|
|
14
|
+
aporteInvestidor: number;
|
|
15
|
+
emprestimoBanco: number;
|
|
16
|
+
despesas: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type OverviewResultsReportData = {
|
|
20
|
+
rows: OverviewResultsRow[];
|
|
21
|
+
totals: {
|
|
22
|
+
faturamento: number;
|
|
23
|
+
despesasEmprestimos: number;
|
|
24
|
+
diferenca: number;
|
|
25
|
+
aporteInvestidor: number;
|
|
26
|
+
emprestimoBanco: number;
|
|
27
|
+
despesas: number;
|
|
28
|
+
margem: number;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type TopCustomerItem = {
|
|
33
|
+
customer: string;
|
|
34
|
+
value: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type TopCustomersReportData = {
|
|
38
|
+
total: number;
|
|
39
|
+
top5Percent: number;
|
|
40
|
+
topCustomers: TopCustomerItem[];
|
|
41
|
+
pieData: TopCustomerItem[];
|
|
42
|
+
groupedPeriods: Array<{ period: string; value: number }>;
|
|
43
|
+
leader: TopCustomerItem | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type TopExpenseItem = {
|
|
47
|
+
category: string;
|
|
48
|
+
costCenter: string;
|
|
49
|
+
label: string;
|
|
50
|
+
value: number;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type TopOperationalExpensesReportData = {
|
|
54
|
+
total: number;
|
|
55
|
+
average: number;
|
|
56
|
+
topExpenses: TopExpenseItem[];
|
|
57
|
+
pieData: Array<{ name: string; value: number }>;
|
|
58
|
+
groupedPeriods: Array<{ period: string; value: number }>;
|
|
59
|
+
highest: TopExpenseItem | null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const EMPTY_OVERVIEW_RESULTS: OverviewResultsReportData = {
|
|
63
|
+
rows: [],
|
|
64
|
+
totals: {
|
|
65
|
+
faturamento: 0,
|
|
66
|
+
despesasEmprestimos: 0,
|
|
67
|
+
diferenca: 0,
|
|
68
|
+
aporteInvestidor: 0,
|
|
69
|
+
emprestimoBanco: 0,
|
|
70
|
+
despesas: 0,
|
|
71
|
+
margem: 0,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const EMPTY_TOP_CUSTOMERS: TopCustomersReportData = {
|
|
76
|
+
total: 0,
|
|
77
|
+
top5Percent: 0,
|
|
78
|
+
topCustomers: [],
|
|
79
|
+
pieData: [],
|
|
80
|
+
groupedPeriods: [],
|
|
81
|
+
leader: null,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const EMPTY_TOP_EXPENSES: TopOperationalExpensesReportData = {
|
|
85
|
+
total: 0,
|
|
86
|
+
average: 0,
|
|
87
|
+
topExpenses: [],
|
|
88
|
+
pieData: [],
|
|
89
|
+
groupedPeriods: [],
|
|
90
|
+
highest: null,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function buildQuery(params: Record<string, QueryValue>) {
|
|
94
|
+
const query = new URLSearchParams();
|
|
95
|
+
|
|
96
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
97
|
+
if (value === undefined || value === null || value === '') {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
query.set(key, String(value));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return query.toString();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toDate(value: string) {
|
|
108
|
+
return new Date(`${value}T00:00:00`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function startOfWeek(date: Date) {
|
|
112
|
+
const current = new Date(date);
|
|
113
|
+
const day = current.getDay();
|
|
114
|
+
const diff = day === 0 ? -6 : 1 - day;
|
|
115
|
+
|
|
116
|
+
current.setDate(current.getDate() + diff);
|
|
117
|
+
current.setHours(0, 0, 0, 0);
|
|
118
|
+
|
|
119
|
+
return current;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getDefaultDateRange() {
|
|
123
|
+
return {
|
|
124
|
+
from: '2021-01-01',
|
|
125
|
+
to: '2026-12-31',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function formatReportBucketLabel(
|
|
130
|
+
bucket: string,
|
|
131
|
+
groupBy: GroupBy,
|
|
132
|
+
locale: string
|
|
133
|
+
) {
|
|
134
|
+
const dateFormatter = new Intl.DateTimeFormat(locale);
|
|
135
|
+
|
|
136
|
+
if (groupBy === 'day') {
|
|
137
|
+
return dateFormatter.format(toDate(bucket));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (groupBy === 'week') {
|
|
141
|
+
const [year, week] = bucket.split('-W');
|
|
142
|
+
|
|
143
|
+
return `W${Number(week)}/${year}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (groupBy === 'month') {
|
|
147
|
+
const [year, month] = bucket.split('-');
|
|
148
|
+
const monthDate = new Date(Number(year), Number(month) - 1, 1);
|
|
149
|
+
|
|
150
|
+
return new Intl.DateTimeFormat(locale, {
|
|
151
|
+
month: 'short',
|
|
152
|
+
year: 'numeric',
|
|
153
|
+
}).format(monthDate);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return bucket;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function useFinanceReportQuery<T>(
|
|
160
|
+
queryKey: string,
|
|
161
|
+
path: string,
|
|
162
|
+
params: Record<string, QueryValue>,
|
|
163
|
+
initialData: T
|
|
164
|
+
) {
|
|
165
|
+
const { request } = useApp();
|
|
166
|
+
const querySuffix = buildQuery(params);
|
|
167
|
+
const url = querySuffix ? `${path}?${querySuffix}` : path;
|
|
168
|
+
|
|
169
|
+
const query = useQuery<T>({
|
|
170
|
+
queryKey: [queryKey, querySuffix || 'default'],
|
|
171
|
+
staleTime: 0,
|
|
172
|
+
refetchOnMount: 'always',
|
|
173
|
+
queryFn: async () => {
|
|
174
|
+
try {
|
|
175
|
+
const response = await request({
|
|
176
|
+
url,
|
|
177
|
+
method: 'GET',
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
...initialData,
|
|
182
|
+
...(response?.data || {}),
|
|
183
|
+
} as T;
|
|
184
|
+
} catch {
|
|
185
|
+
return initialData;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
initialData,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
...query,
|
|
193
|
+
data: query.data ?? initialData,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function useOverviewResultsReport(filters: {
|
|
198
|
+
from: string;
|
|
199
|
+
to: string;
|
|
200
|
+
groupBy: GroupBy;
|
|
201
|
+
}) {
|
|
202
|
+
return useFinanceReportQuery<OverviewResultsReportData>(
|
|
203
|
+
'finance-overview-results-report',
|
|
204
|
+
'/finance/reports/overview-results',
|
|
205
|
+
filters,
|
|
206
|
+
EMPTY_OVERVIEW_RESULTS
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function useTopCustomersReport(filters: {
|
|
211
|
+
from: string;
|
|
212
|
+
to: string;
|
|
213
|
+
groupBy: GroupBy;
|
|
214
|
+
search?: string;
|
|
215
|
+
topN?: number;
|
|
216
|
+
}) {
|
|
217
|
+
return useFinanceReportQuery<TopCustomersReportData>(
|
|
218
|
+
'finance-top-customers-report',
|
|
219
|
+
'/finance/reports/top-customers',
|
|
220
|
+
filters,
|
|
221
|
+
EMPTY_TOP_CUSTOMERS
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function useTopOperationalExpensesReport(filters: {
|
|
226
|
+
from: string;
|
|
227
|
+
to: string;
|
|
228
|
+
groupBy: GroupBy;
|
|
229
|
+
search?: string;
|
|
230
|
+
topN?: number;
|
|
231
|
+
}) {
|
|
232
|
+
return useFinanceReportQuery<TopOperationalExpensesReportData>(
|
|
233
|
+
'finance-top-operational-expenses-report',
|
|
234
|
+
'/finance/reports/top-operational-expenses',
|
|
235
|
+
filters,
|
|
236
|
+
EMPTY_TOP_EXPENSES
|
|
237
|
+
);
|
|
238
|
+
}
|