@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
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+
3
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
4
+ import { Card, CardContent } from '@/components/ui/card';
5
+ import { useWidgetData } from '@/hooks/use-widget-data';
6
+ import { ClipboardList } from 'lucide-react';
7
+ import { useTranslations } from 'next-intl';
8
+
9
+ interface PendingApprovalsKpiProps {
10
+ widget?: { name?: string };
11
+ onRemove?: () => void;
12
+ }
13
+
14
+ interface FinanceData {
15
+ aprovacoesPendentes?: Array<{
16
+ valor?: number | null;
17
+ }>;
18
+ }
19
+
20
+ export default function PendingApprovalsKpi({
21
+ widget,
22
+ onRemove,
23
+ }: PendingApprovalsKpiProps) {
24
+ const t = useTranslations('finance.DashboardPage');
25
+
26
+ const { data, isLoading, isAccessDenied, isError } =
27
+ useWidgetData<FinanceData>({
28
+ endpoint: '/finance/data',
29
+ queryKey: 'finance-kpi-pending-approvals',
30
+ });
31
+
32
+ const pendingApprovals = data?.aprovacoesPendentes || [];
33
+ const count = pendingApprovals.length;
34
+ const totalValue = pendingApprovals.reduce(
35
+ (acc, item) => acc + Number(item?.valor || 0),
36
+ 0
37
+ );
38
+ const formattedValue = new Intl.NumberFormat('pt-BR', {
39
+ style: 'currency',
40
+ currency: 'BRL',
41
+ }).format(totalValue);
42
+
43
+ return (
44
+ <WidgetWrapper
45
+ isLoading={isLoading}
46
+ isAccessDenied={isAccessDenied}
47
+ isError={isError}
48
+ widgetName={widget?.name || t('kpis.pendingApprovals.title')}
49
+ onRemove={onRemove}
50
+ >
51
+ <Card className="h-full overflow-hidden border-border/60 bg-linear-to-br from-background to-sky-50/50 shadow-sm transition-all duration-300 hover:-translate-y-0.5 hover:shadow-md">
52
+ <CardContent className="flex h-full items-center gap-2.5 p-3">
53
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-sky-100/80">
54
+ <ClipboardList className="h-4.5 w-4.5 text-sky-600" />
55
+ </div>
56
+ <div className="flex min-w-0 flex-1 flex-col justify-center">
57
+ <span className="text-[9px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
58
+ {t('kpis.pendingApprovals.title')}
59
+ </span>
60
+ <div className="flex items-end gap-2">
61
+ <span className="truncate text-base font-bold leading-none tracking-tight text-foreground sm:text-lg">
62
+ {count}
63
+ </span>
64
+ <span className="mb-0.5 text-[9px] text-muted-foreground">
65
+ {t('kpis.pendingApprovals.countLabel')}
66
+ </span>
67
+ </div>
68
+ <span className="truncate text-[9px] text-muted-foreground">
69
+ {t('kpis.pendingApprovals.description', {
70
+ value: formattedValue,
71
+ })}
72
+ </span>
73
+ </div>
74
+ </CardContent>
75
+ </Card>
76
+ </WidgetWrapper>
77
+ );
78
+ }
@@ -0,0 +1,147 @@
1
+ 'use client';
2
+
3
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
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 { Money } from '@/components/ui/money';
13
+ import { useWidgetData } from '@/hooks/use-widget-data';
14
+ import { ClipboardCheck } from 'lucide-react';
15
+ import { useTranslations } from 'next-intl';
16
+ import Link from 'next/link';
17
+
18
+ interface PendingApprovalsListProps {
19
+ widget?: { name?: string };
20
+ onRemove?: () => void;
21
+ }
22
+
23
+ interface ApprovalItem {
24
+ id: string;
25
+ tituloId?: string;
26
+ valor?: number | null;
27
+ politica?: string | null;
28
+ urgencia?: string | null;
29
+ dataSolicitacao?: string | null;
30
+ }
31
+
32
+ interface FinanceData {
33
+ aprovacoesPendentes?: ApprovalItem[];
34
+ }
35
+
36
+ function formatDate(value?: string | null) {
37
+ if (!value) return '—';
38
+
39
+ const parsed = new Date(value);
40
+ return Number.isNaN(parsed.getTime())
41
+ ? '—'
42
+ : parsed.toLocaleDateString('pt-BR');
43
+ }
44
+
45
+ export default function PendingApprovalsList({
46
+ widget,
47
+ onRemove,
48
+ }: PendingApprovalsListProps) {
49
+ const t = useTranslations('finance.DashboardPage');
50
+ const approvalT = useTranslations('finance.PayableApprovalsPage');
51
+
52
+ const { data, isLoading, isAccessDenied, isError } =
53
+ useWidgetData<FinanceData>({
54
+ endpoint: '/finance/data',
55
+ queryKey: 'finance-pending-approvals-list',
56
+ });
57
+
58
+ const rows = [...(data?.aprovacoesPendentes || [])]
59
+ .sort(
60
+ (a, b) =>
61
+ new Date(b?.dataSolicitacao || 0).getTime() -
62
+ new Date(a?.dataSolicitacao || 0).getTime()
63
+ )
64
+ .slice(0, 4);
65
+
66
+ const defaultUrgency = {
67
+ label: approvalT('urgency.medium'),
68
+ className: 'bg-blue-100 text-blue-700',
69
+ };
70
+
71
+ const urgencyConfig: Record<
72
+ string,
73
+ { label: string; className: string }
74
+ > = {
75
+ baixa: {
76
+ label: approvalT('urgency.low'),
77
+ className: 'bg-slate-100 text-slate-700',
78
+ },
79
+ media: defaultUrgency,
80
+ alta: {
81
+ label: approvalT('urgency.high'),
82
+ className: 'bg-orange-100 text-orange-700',
83
+ },
84
+ critica: {
85
+ label: approvalT('urgency.critical'),
86
+ className: 'bg-red-100 text-red-700',
87
+ },
88
+ };
89
+
90
+ return (
91
+ <WidgetWrapper
92
+ isLoading={isLoading}
93
+ isAccessDenied={isAccessDenied}
94
+ isError={isError}
95
+ widgetName={widget?.name || t('approvalQueue.title')}
96
+ onRemove={onRemove}
97
+ >
98
+ <Card className="h-full">
99
+ <CardHeader className="pb-2">
100
+ <CardTitle className="flex items-center gap-2 text-base">
101
+ <ClipboardCheck className="h-4 w-4 text-sky-600" />
102
+ {t('approvalQueue.title')}
103
+ </CardTitle>
104
+ <CardDescription>{t('approvalQueue.description')}</CardDescription>
105
+ </CardHeader>
106
+ <CardContent>
107
+ <div className="space-y-2.5">
108
+ {rows.map((item) => {
109
+ const urgency =
110
+ urgencyConfig[String(item.urgencia || 'media').toLowerCase()] ??
111
+ defaultUrgency;
112
+
113
+ return (
114
+ <Link
115
+ key={item.id}
116
+ href="/finance/accounts-payable/approvals"
117
+ className="flex cursor-pointer items-center justify-between rounded-md border border-border/60 px-3 py-2 transition-colors hover:bg-muted/50 active:bg-muted"
118
+ >
119
+ <div className="min-w-0 pr-3">
120
+ <p className="truncate text-sm font-medium text-foreground">
121
+ {item.politica || t('approvalQueue.defaultPolicy')}
122
+ </p>
123
+ <p className="text-[11px] text-muted-foreground">
124
+ {formatDate(item.dataSolicitacao)}
125
+ </p>
126
+ </div>
127
+ <div className="flex items-center gap-2">
128
+ <div className="text-right text-sm font-medium text-foreground">
129
+ <Money value={Number(item.valor || 0)} />
130
+ </div>
131
+ <Badge className={urgency.className}>{urgency.label}</Badge>
132
+ </div>
133
+ </Link>
134
+ );
135
+ })}
136
+
137
+ {rows.length === 0 && (
138
+ <div className="rounded-md border border-dashed border-border/70 px-3 py-5 text-center text-sm text-muted-foreground">
139
+ {t('approvalQueue.empty')}
140
+ </div>
141
+ )}
142
+ </div>
143
+ </CardContent>
144
+ </Card>
145
+ </WidgetWrapper>
146
+ );
147
+ }
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
4
+ import { Card, CardContent } from '@/components/ui/card';
5
+ import { useWidgetData } from '@/hooks/use-widget-data';
6
+ import { ArrowLeftRight } from 'lucide-react';
7
+ import { useTranslations } from 'next-intl';
8
+
9
+ interface PendingReconciliationKpiProps {
10
+ widget?: { name?: string };
11
+ onRemove?: () => void;
12
+ }
13
+
14
+ interface FinanceData {
15
+ extratos?: Array<{
16
+ statusConciliacao?: string | null;
17
+ }>;
18
+ contasBancarias?: Array<{
19
+ ativo?: boolean | null;
20
+ }>;
21
+ }
22
+
23
+ const normalize = (value?: string | null) =>
24
+ String(value || '')
25
+ .normalize('NFD')
26
+ .replace(/[\u0300-\u036f]/g, '')
27
+ .toLowerCase();
28
+
29
+ export default function PendingReconciliationKpi({
30
+ widget,
31
+ onRemove,
32
+ }: PendingReconciliationKpiProps) {
33
+ const t = useTranslations('finance.DashboardPage');
34
+
35
+ const { data, isLoading, isAccessDenied, isError } =
36
+ useWidgetData<FinanceData>({
37
+ endpoint: '/finance/data',
38
+ queryKey: 'finance-kpi-pending-reconciliation',
39
+ });
40
+
41
+ const pendingCount = (data?.extratos || []).filter((statement) =>
42
+ normalize(statement?.statusConciliacao).includes('pend')
43
+ ).length;
44
+
45
+ const activeAccounts = (data?.contasBancarias || []).filter(
46
+ (account) => account?.ativo !== false
47
+ ).length;
48
+
49
+ return (
50
+ <WidgetWrapper
51
+ isLoading={isLoading}
52
+ isAccessDenied={isAccessDenied}
53
+ isError={isError}
54
+ widgetName={widget?.name || t('kpis.pendingReconciliation.title')}
55
+ onRemove={onRemove}
56
+ >
57
+ <Card className="h-full overflow-hidden border-border/60 bg-linear-to-br from-background to-violet-50/50 shadow-sm transition-all duration-300 hover:-translate-y-0.5 hover:shadow-md">
58
+ <CardContent className="flex h-full items-center gap-2.5 p-3">
59
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-violet-100/80">
60
+ <ArrowLeftRight className="h-4.5 w-4.5 text-violet-600" />
61
+ </div>
62
+ <div className="flex min-w-0 flex-1 flex-col justify-center">
63
+ <span className="truncate text-[9px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
64
+ {t('kpis.pendingReconciliation.title')}
65
+ </span>
66
+ <div className="flex items-end gap-2">
67
+ <span className="truncate text-base font-bold leading-none tracking-tight text-foreground sm:text-lg">
68
+ {pendingCount}
69
+ </span>
70
+ <span className="mb-0.5 truncate text-[9px] text-muted-foreground">
71
+ {t('kpis.pendingReconciliation.countLabel')}
72
+ </span>
73
+ </div>
74
+ <span className="truncate text-[9px] text-muted-foreground">
75
+ {t('kpis.pendingReconciliation.description', {
76
+ value: activeAccounts,
77
+ })}
78
+ </span>
79
+ </div>
80
+ </CardContent>
81
+ </Card>
82
+ </WidgetWrapper>
83
+ );
84
+ }
@@ -4,7 +4,7 @@ import { Card, CardContent } from '@/components/ui/card';
4
4
  import { useWidgetData } from '@/hooks/use-widget-data';
5
5
  import { TrendingUp } from 'lucide-react';
6
6
  import { useTranslations } from 'next-intl';
7
- import { WidgetWrapper } from '../widget-wrapper';
7
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
8
8
 
9
9
  interface Receivable30dKpiProps {
10
10
  widget?: { name?: string };
@@ -45,19 +45,19 @@ export default function Receivable30dKpi({
45
45
  widgetName={widget?.name || t('kpis.receivable30.title')}
46
46
  onRemove={onRemove}
47
47
  >
48
- <Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
49
- <CardContent className="flex h-full items-center gap-3 p-4">
50
- <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-green-50">
51
- <TrendingUp className="h-5 w-5 text-green-600" />
48
+ <Card className="h-full overflow-hidden border-border/60 bg-linear-to-br from-background to-green-50/50 shadow-sm transition-all duration-300 hover:-translate-y-0.5 hover:shadow-md">
49
+ <CardContent className="flex h-full items-center gap-2.5 p-3">
50
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-green-100/80">
51
+ <TrendingUp className="h-4.5 w-4.5 text-green-600" />
52
52
  </div>
53
- <div className="flex min-w-0 flex-col">
54
- <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
53
+ <div className="flex min-w-0 flex-1 flex-col justify-center">
54
+ <span className="text-[9px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
55
55
  {t('kpis.receivable30.title')}
56
56
  </span>
57
- <span className="truncate text-lg font-bold tracking-tight text-foreground">
57
+ <span className="truncate text-base font-bold leading-none tracking-tight text-foreground sm:text-lg">
58
58
  {formatted}
59
59
  </span>
60
- <span className="text-[10px] text-muted-foreground">
60
+ <span className="truncate text-[9px] text-muted-foreground">
61
61
  {t('kpis.receivable30.sevenDays', {
62
62
  value: new Intl.NumberFormat('pt-BR', {
63
63
  style: 'currency',
@@ -0,0 +1,163 @@
1
+ 'use client';
2
+
3
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@/components/ui/card';
11
+ import { Money } from '@/components/ui/money';
12
+ import { useWidgetData } from '@/hooks/use-widget-data';
13
+ import { BarChart3 } from 'lucide-react';
14
+ import { useTranslations } from 'next-intl';
15
+
16
+ interface AgingWidgetProps {
17
+ widget?: { name?: string };
18
+ onRemove?: () => void;
19
+ }
20
+
21
+ interface AgingItem {
22
+ bucket0_30?: number | null;
23
+ bucket31_60?: number | null;
24
+ bucket61_90?: number | null;
25
+ bucket90plus?: number | null;
26
+ total?: number | null;
27
+ }
28
+
29
+ interface FinanceData {
30
+ agingInadimplencia?: AgingItem[];
31
+ }
32
+
33
+ export default function ReceivableAgingAnalysis({
34
+ widget,
35
+ onRemove,
36
+ }: AgingWidgetProps) {
37
+ const t = useTranslations('finance.DashboardPage');
38
+
39
+ const { data, isLoading, isAccessDenied, isError } =
40
+ useWidgetData<FinanceData>({
41
+ endpoint: '/finance/data',
42
+ queryKey: 'finance-receivable-aging-analysis',
43
+ });
44
+
45
+ const agingItems = data?.agingInadimplencia || [];
46
+
47
+ const totals = {
48
+ bucket0to30: agingItems.reduce(
49
+ (acc, item) => acc + Number(item?.bucket0_30 || 0),
50
+ 0
51
+ ),
52
+ bucket31to60: agingItems.reduce(
53
+ (acc, item) => acc + Number(item?.bucket31_60 || 0),
54
+ 0
55
+ ),
56
+ bucket61to90: agingItems.reduce(
57
+ (acc, item) => acc + Number(item?.bucket61_90 || 0),
58
+ 0
59
+ ),
60
+ bucket90plus: agingItems.reduce(
61
+ (acc, item) => acc + Number(item?.bucket90plus || 0),
62
+ 0
63
+ ),
64
+ total: agingItems.reduce((acc, item) => acc + Number(item?.total || 0), 0),
65
+ clients: agingItems.length,
66
+ };
67
+
68
+ const buckets = [
69
+ {
70
+ label: t('aging.range0to30'),
71
+ value: totals.bucket0to30,
72
+ colorClass: 'bg-amber-400',
73
+ },
74
+ {
75
+ label: t('aging.range31to60'),
76
+ value: totals.bucket31to60,
77
+ colorClass: 'bg-orange-400',
78
+ },
79
+ {
80
+ label: t('aging.range61to90'),
81
+ value: totals.bucket61to90,
82
+ colorClass: 'bg-rose-400',
83
+ },
84
+ {
85
+ label: t('aging.range90plus'),
86
+ value: totals.bucket90plus,
87
+ colorClass: 'bg-red-500',
88
+ },
89
+ ];
90
+
91
+ return (
92
+ <WidgetWrapper
93
+ isLoading={isLoading}
94
+ isAccessDenied={isAccessDenied}
95
+ isError={isError}
96
+ widgetName={widget?.name || t('aging.title')}
97
+ onRemove={onRemove}
98
+ >
99
+ <Card className="flex h-full flex-col overflow-hidden">
100
+ <CardHeader className="pb-1.5">
101
+ <CardTitle className="flex items-center gap-2 text-sm font-semibold">
102
+ <BarChart3 className="h-4 w-4 text-rose-500" />
103
+ {t('aging.title')}
104
+ </CardTitle>
105
+ <CardDescription className="line-clamp-1 text-xs">
106
+ {t('aging.description')}
107
+ </CardDescription>
108
+ </CardHeader>
109
+ <CardContent className="flex flex-1 flex-col gap-2 overflow-hidden pt-0">
110
+ <div className="grid grid-cols-2 gap-2 rounded-lg bg-muted/40 p-2.5">
111
+ <div>
112
+ <p className="text-[10px] uppercase tracking-[0.16em] text-muted-foreground">
113
+ {t('aging.totalLabel')}
114
+ </p>
115
+ <p className="text-sm font-semibold text-foreground sm:text-base">
116
+ <Money value={totals.total} />
117
+ </p>
118
+ </div>
119
+ <div>
120
+ <p className="text-[10px] uppercase tracking-[0.16em] text-muted-foreground">
121
+ {t('aging.clientsLabel')}
122
+ </p>
123
+ <p className="text-sm font-semibold text-foreground sm:text-base">
124
+ {totals.clients}
125
+ </p>
126
+ </div>
127
+ </div>
128
+
129
+ <div className="grid min-h-0 grid-cols-2 gap-2">
130
+ {buckets.map((bucket) => {
131
+ const percentage =
132
+ totals.total > 0 ? (bucket.value / totals.total) * 100 : 0;
133
+
134
+ return (
135
+ <div
136
+ key={bucket.label}
137
+ className="rounded-md border border-border/60 bg-background/70 p-2"
138
+ >
139
+ <div className="mb-1 flex items-start justify-between gap-2">
140
+ <span className="text-[11px] leading-4 text-muted-foreground">
141
+ {bucket.label}
142
+ </span>
143
+ <span className="shrink-0 text-[11px] font-medium text-foreground">
144
+ <Money value={bucket.value} />
145
+ </span>
146
+ </div>
147
+ <div className="h-1.5 overflow-hidden rounded-full bg-muted">
148
+ <div
149
+ className={`h-full rounded-full ${bucket.colorClass}`}
150
+ style={{
151
+ width: `${Math.max(percentage, bucket.value > 0 ? 8 : 0)}%`,
152
+ }}
153
+ />
154
+ </div>
155
+ </div>
156
+ );
157
+ })}
158
+ </div>
159
+ </CardContent>
160
+ </Card>
161
+ </WidgetWrapper>
162
+ );
163
+ }
@@ -13,7 +13,7 @@ import { useWidgetData } from '@/hooks/use-widget-data';
13
13
  import { ArrowDownRight } from 'lucide-react';
14
14
  import { useTranslations } from 'next-intl';
15
15
  import Link from 'next/link';
16
- import { WidgetWrapper } from '../widget-wrapper';
16
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
17
17
 
18
18
  interface FinanceData {
19
19
  titulosPagar?: Array<{
@@ -13,7 +13,7 @@ import { useWidgetData } from '@/hooks/use-widget-data';
13
13
  import { ArrowUpRight } from 'lucide-react';
14
14
  import { useTranslations } from 'next-intl';
15
15
  import Link from 'next/link';
16
- import { WidgetWrapper } from '../widget-wrapper';
16
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
17
17
 
18
18
  interface FinanceData {
19
19
  titulosReceber?: Array<{
@@ -25,6 +25,13 @@ columns:
25
25
  - name: account_type
26
26
  type: enum
27
27
  values: [checking, savings, investment, cash, other]
28
+ - name: logo_file_id
29
+ type: fk
30
+ isNullable: true
31
+ references:
32
+ table: file
33
+ column: id
34
+ onDelete: SET NULL
28
35
  - name: status
29
36
  type: enum
30
37
  values: [active, inactive]
@@ -34,4 +41,5 @@ columns:
34
41
  indices:
35
42
  - columns: [code]
36
43
  isUnique: true
44
+ - columns: [logo_file_id]
37
45
  - columns: [status]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/finance",
3
- "version": "0.0.299",
3
+ "version": "0.0.301",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -10,13 +10,13 @@
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api": "0.0.6",
13
- "@hed-hog/api-locale": "0.0.14",
14
13
  "@hed-hog/api-pagination": "0.0.7",
15
- "@hed-hog/tag": "0.0.299",
16
- "@hed-hog/core": "0.0.299",
17
- "@hed-hog/contact": "0.0.299",
14
+ "@hed-hog/api-locale": "0.0.14",
15
+ "@hed-hog/core": "0.0.301",
18
16
  "@hed-hog/api-prisma": "0.0.6",
19
- "@hed-hog/api-types": "0.0.1"
17
+ "@hed-hog/tag": "0.0.301",
18
+ "@hed-hog/api-types": "0.0.1",
19
+ "@hed-hog/contact": "0.0.301"
20
20
  },
21
21
  "exports": {
22
22
  ".": {
@@ -32,6 +32,7 @@
32
32
  ],
33
33
  "scripts": {
34
34
  "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
35
+ "test": "jest --config jest.config.ts --runInBand",
35
36
  "prebuild": "pnpm --dir ../.. exec ts-node ./scripts/build-dependencies.ts libraries/finance",
36
37
  "build": "tsc --project tsconfig.production.json",
37
38
  "patch": "pnpm exec ts-node ../../scripts/patch.ts libraries/finance",
@@ -1,5 +1,5 @@
1
1
  import { getLocaleText } from '@hed-hog/api-locale';
2
- import { IsNumber, IsOptional, IsString, Min } from 'class-validator';
2
+ import { IsInt, IsNumber, IsOptional, IsString, Min } from 'class-validator';
3
3
 
4
4
  export class CreateBankAccountDto {
5
5
  @IsString({
@@ -34,6 +34,12 @@ export class CreateBankAccountDto {
34
34
  })
35
35
  description?: string;
36
36
 
37
+ @IsOptional()
38
+ @IsInt({
39
+ message: (args) => getLocaleText('validation.idMustBeInteger', args.value),
40
+ })
41
+ logo_file_id?: number | null;
42
+
37
43
  @IsOptional()
38
44
  @IsNumber(
39
45
  {},
@@ -1,5 +1,5 @@
1
1
  import { getLocaleText } from '@hed-hog/api-locale';
2
- import { IsOptional, IsString } from 'class-validator';
2
+ import { IsInt, IsOptional, IsString } from 'class-validator';
3
3
 
4
4
  export class UpdateBankAccountDto {
5
5
  @IsOptional()
@@ -36,6 +36,12 @@ export class UpdateBankAccountDto {
36
36
  })
37
37
  description?: string;
38
38
 
39
+ @IsOptional()
40
+ @IsInt({
41
+ message: (args) => getLocaleText('validation.idMustBeInteger', args.value),
42
+ })
43
+ logo_file_id?: number | null;
44
+
39
45
  @IsOptional()
40
46
  @IsString({
41
47
  message: (args) =>