@hed-hog/finance 0.0.3 → 0.0.224
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/hedhog/data/menu.yaml +18 -23
- package/hedhog/frontend/app/_lib/formatters.ts.ejs +20 -0
- package/hedhog/frontend/app/_lib/use-finance-data.ts.ejs +87 -0
- package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +265 -0
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +388 -0
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +364 -0
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +388 -0
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +385 -0
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +364 -0
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +274 -0
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +401 -0
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +266 -0
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +244 -0
- package/hedhog/frontend/app/page.tsx.ejs +313 -15
- package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +285 -0
- package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +195 -0
- package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +321 -0
- package/hedhog/query/constraints.sql +169 -0
- package/package.json +4 -4
package/hedhog/data/menu.yaml
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
en: Finance
|
|
5
5
|
pt: Finanças
|
|
6
6
|
slug: /finance
|
|
7
|
+
order: 100
|
|
7
8
|
relations:
|
|
8
9
|
role:
|
|
9
10
|
- where:
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
en: Payable Bills
|
|
17
18
|
pt: Contas a Pagar
|
|
18
19
|
slug: /finance/accounts-payable
|
|
20
|
+
order: 101
|
|
19
21
|
relations:
|
|
20
22
|
role:
|
|
21
23
|
- where:
|
|
@@ -58,6 +60,7 @@
|
|
|
58
60
|
en: Receivable Bills
|
|
59
61
|
pt: Contas a Receber
|
|
60
62
|
slug: /finance/accounts-receivable
|
|
63
|
+
order: 102
|
|
61
64
|
relations:
|
|
62
65
|
role:
|
|
63
66
|
- where:
|
|
@@ -100,6 +103,7 @@
|
|
|
100
103
|
en: Cash and Banks
|
|
101
104
|
pt: Caixa e Bancos
|
|
102
105
|
slug: /finance/cash-and-banks
|
|
106
|
+
order: 103
|
|
103
107
|
relations:
|
|
104
108
|
role:
|
|
105
109
|
- where:
|
|
@@ -172,6 +176,7 @@
|
|
|
172
176
|
en: Planning
|
|
173
177
|
pt: Planejamento
|
|
174
178
|
slug: /finance/planning
|
|
179
|
+
order: 104
|
|
175
180
|
relations:
|
|
176
181
|
role:
|
|
177
182
|
- where:
|
|
@@ -229,6 +234,7 @@
|
|
|
229
234
|
en: Reports
|
|
230
235
|
pt: Relatórios
|
|
231
236
|
slug: /finance/reports
|
|
237
|
+
order: 105
|
|
232
238
|
relations:
|
|
233
239
|
role:
|
|
234
240
|
- where:
|
|
@@ -281,26 +287,15 @@
|
|
|
281
287
|
- where:
|
|
282
288
|
slug: admin-finance
|
|
283
289
|
|
|
284
|
-
- icon: settings
|
|
285
|
-
name:
|
|
286
|
-
en: Administration
|
|
287
|
-
pt: Administração
|
|
288
|
-
slug: /finance/administration
|
|
289
|
-
relations:
|
|
290
|
-
role:
|
|
291
|
-
- where:
|
|
292
|
-
slug: admin
|
|
293
|
-
- where:
|
|
294
|
-
slug: admin-finance
|
|
295
290
|
- menu_id:
|
|
296
291
|
where:
|
|
297
|
-
slug: /
|
|
292
|
+
slug: /core/management
|
|
298
293
|
icon: clipboard-list
|
|
299
|
-
url: /
|
|
294
|
+
url: /core/management/audit-logs
|
|
300
295
|
name:
|
|
301
296
|
en: Audit and Logs
|
|
302
297
|
pt: Auditoria e Logs
|
|
303
|
-
slug: /
|
|
298
|
+
slug: /core/management/audit-logs
|
|
304
299
|
relations:
|
|
305
300
|
role:
|
|
306
301
|
- where:
|
|
@@ -309,13 +304,13 @@
|
|
|
309
304
|
slug: admin-finance
|
|
310
305
|
- menu_id:
|
|
311
306
|
where:
|
|
312
|
-
slug: /
|
|
307
|
+
slug: /core/management
|
|
313
308
|
icon: calendar-check-2
|
|
314
|
-
url: /
|
|
309
|
+
url: /core/management/period-close
|
|
315
310
|
name:
|
|
316
311
|
en: Period Close
|
|
317
312
|
pt: Fechamento de Período
|
|
318
|
-
slug: /
|
|
313
|
+
slug: /core/management/period-close
|
|
319
314
|
relations:
|
|
320
315
|
role:
|
|
321
316
|
- where:
|
|
@@ -324,13 +319,13 @@
|
|
|
324
319
|
slug: admin-finance
|
|
325
320
|
- menu_id:
|
|
326
321
|
where:
|
|
327
|
-
slug: /
|
|
322
|
+
slug: /core/management
|
|
328
323
|
icon: folder-tree
|
|
329
|
-
url: /
|
|
324
|
+
url: /core/management/categories
|
|
330
325
|
name:
|
|
331
326
|
en: Categories
|
|
332
327
|
pt: Categorias
|
|
333
|
-
slug: /
|
|
328
|
+
slug: /core/management/categories
|
|
334
329
|
relations:
|
|
335
330
|
role:
|
|
336
331
|
- where:
|
|
@@ -339,13 +334,13 @@
|
|
|
339
334
|
slug: admin-finance
|
|
340
335
|
- menu_id:
|
|
341
336
|
where:
|
|
342
|
-
slug: /
|
|
337
|
+
slug: /core/management
|
|
343
338
|
icon: pie-chart
|
|
344
|
-
url: /
|
|
339
|
+
url: /core/management/cost-centers
|
|
345
340
|
name:
|
|
346
341
|
en: Cost Centers
|
|
347
342
|
pt: Centros de Custo
|
|
348
|
-
slug: /
|
|
343
|
+
slug: /core/management/cost-centers
|
|
349
344
|
relations:
|
|
350
345
|
role:
|
|
351
346
|
- where:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function formatarData(value?: string | Date | null) {
|
|
2
|
+
if (!value) {
|
|
3
|
+
return '-';
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
7
|
+
|
|
8
|
+
if (Number.isNaN(date.getTime())) {
|
|
9
|
+
return String(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return new Intl.DateTimeFormat('pt-BR').format(date);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatarMoeda(value?: number | null) {
|
|
16
|
+
return new Intl.NumberFormat('pt-BR', {
|
|
17
|
+
style: 'currency',
|
|
18
|
+
currency: 'BRL',
|
|
19
|
+
}).format(value || 0);
|
|
20
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
4
|
+
|
|
5
|
+
type FinanceData = {
|
|
6
|
+
kpis: {
|
|
7
|
+
saldoCaixa: number;
|
|
8
|
+
aPagar30dias: number;
|
|
9
|
+
aPagar7dias: number;
|
|
10
|
+
aReceber30dias: number;
|
|
11
|
+
aReceber7dias: number;
|
|
12
|
+
inadimplencia: number;
|
|
13
|
+
};
|
|
14
|
+
fluxoCaixaPrevisto: any[];
|
|
15
|
+
titulosPagar: any[];
|
|
16
|
+
titulosReceber: any[];
|
|
17
|
+
extratos: any[];
|
|
18
|
+
contasBancarias: any[];
|
|
19
|
+
pessoas: any[];
|
|
20
|
+
categorias: any[];
|
|
21
|
+
centrosCusto: any[];
|
|
22
|
+
aprovacoesPendentes: any[];
|
|
23
|
+
agingInadimplencia: any[];
|
|
24
|
+
cenarios: any[];
|
|
25
|
+
transferencias: any[];
|
|
26
|
+
tags: any[];
|
|
27
|
+
logsAuditoria: any[];
|
|
28
|
+
recebiveis: any[];
|
|
29
|
+
adquirentes: any[];
|
|
30
|
+
historicoContatos: any[];
|
|
31
|
+
entradasPrevistas: any[];
|
|
32
|
+
saidasPrevistas: any[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const EMPTY_FINANCE_DATA: FinanceData = {
|
|
36
|
+
kpis: {
|
|
37
|
+
saldoCaixa: 0,
|
|
38
|
+
aPagar30dias: 0,
|
|
39
|
+
aPagar7dias: 0,
|
|
40
|
+
aReceber30dias: 0,
|
|
41
|
+
aReceber7dias: 0,
|
|
42
|
+
inadimplencia: 0,
|
|
43
|
+
},
|
|
44
|
+
fluxoCaixaPrevisto: [],
|
|
45
|
+
titulosPagar: [],
|
|
46
|
+
titulosReceber: [],
|
|
47
|
+
extratos: [],
|
|
48
|
+
contasBancarias: [],
|
|
49
|
+
pessoas: [],
|
|
50
|
+
categorias: [],
|
|
51
|
+
centrosCusto: [],
|
|
52
|
+
aprovacoesPendentes: [],
|
|
53
|
+
agingInadimplencia: [],
|
|
54
|
+
cenarios: [],
|
|
55
|
+
transferencias: [],
|
|
56
|
+
tags: [],
|
|
57
|
+
logsAuditoria: [],
|
|
58
|
+
recebiveis: [],
|
|
59
|
+
adquirentes: [],
|
|
60
|
+
historicoContatos: [],
|
|
61
|
+
entradasPrevistas: [],
|
|
62
|
+
saidasPrevistas: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function useFinanceData() {
|
|
66
|
+
const { request } = useApp();
|
|
67
|
+
|
|
68
|
+
return useQuery<FinanceData>({
|
|
69
|
+
queryKey: ['finance-data'],
|
|
70
|
+
queryFn: async () => {
|
|
71
|
+
try {
|
|
72
|
+
const response = await request({
|
|
73
|
+
url: '/finance/data',
|
|
74
|
+
method: 'GET',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
...EMPTY_FINANCE_DATA,
|
|
79
|
+
...(response?.data || {}),
|
|
80
|
+
} as FinanceData;
|
|
81
|
+
} catch {
|
|
82
|
+
return EMPTY_FINANCE_DATA;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
initialData: EMPTY_FINANCE_DATA,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Badge } from '@/components/ui/badge';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from '@/components/ui/card';
|
|
12
|
+
import {
|
|
13
|
+
Dialog,
|
|
14
|
+
DialogContent,
|
|
15
|
+
DialogDescription,
|
|
16
|
+
DialogFooter,
|
|
17
|
+
DialogHeader,
|
|
18
|
+
DialogTitle,
|
|
19
|
+
DialogTrigger,
|
|
20
|
+
} from '@/components/ui/dialog';
|
|
21
|
+
import { Label } from '@/components/ui/label';
|
|
22
|
+
import { Money } from '@/components/ui/money';
|
|
23
|
+
import { PageHeader } from '@/components/ui/page-header';
|
|
24
|
+
import {
|
|
25
|
+
Table,
|
|
26
|
+
TableBody,
|
|
27
|
+
TableCell,
|
|
28
|
+
TableHead,
|
|
29
|
+
TableHeader,
|
|
30
|
+
TableRow,
|
|
31
|
+
} from '@/components/ui/table';
|
|
32
|
+
import { Textarea } from '@/components/ui/textarea';
|
|
33
|
+
import { AlertTriangle, CheckCircle, Clock, XCircle } from 'lucide-react';
|
|
34
|
+
import { useEffect, useState } from 'react';
|
|
35
|
+
import { formatarData } from '../../_lib/formatters';
|
|
36
|
+
import { useFinanceData } from '../../_lib/use-finance-data';
|
|
37
|
+
|
|
38
|
+
const urgenciaConfig = {
|
|
39
|
+
baixa: { label: 'Baixa', className: 'bg-slate-100 text-slate-700' },
|
|
40
|
+
media: { label: 'Média', className: 'bg-blue-100 text-blue-700' },
|
|
41
|
+
alta: { label: 'Alta', className: 'bg-orange-100 text-orange-700' },
|
|
42
|
+
critica: { label: 'Crítica', className: 'bg-red-100 text-red-700' },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function AprovacaoDialog({
|
|
46
|
+
tipo,
|
|
47
|
+
onConfirm,
|
|
48
|
+
}: {
|
|
49
|
+
tipo: 'aprovar' | 'reprovar';
|
|
50
|
+
onConfirm: () => void;
|
|
51
|
+
}) {
|
|
52
|
+
const isAprovar = tipo === 'aprovar';
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Dialog>
|
|
56
|
+
<DialogTrigger asChild>
|
|
57
|
+
<Button variant={isAprovar ? 'default' : 'outline'} size="sm">
|
|
58
|
+
{isAprovar ? (
|
|
59
|
+
<>
|
|
60
|
+
<CheckCircle className="mr-2 h-4 w-4" />
|
|
61
|
+
Aprovar
|
|
62
|
+
</>
|
|
63
|
+
) : (
|
|
64
|
+
<>
|
|
65
|
+
<XCircle className="mr-2 h-4 w-4" />
|
|
66
|
+
Reprovar
|
|
67
|
+
</>
|
|
68
|
+
)}
|
|
69
|
+
</Button>
|
|
70
|
+
</DialogTrigger>
|
|
71
|
+
<DialogContent>
|
|
72
|
+
<DialogHeader>
|
|
73
|
+
<DialogTitle>
|
|
74
|
+
{isAprovar ? 'Aprovar Título' : 'Reprovar Título'}
|
|
75
|
+
</DialogTitle>
|
|
76
|
+
<DialogDescription>
|
|
77
|
+
{isAprovar
|
|
78
|
+
? 'Confirme a aprovação deste título para pagamento.'
|
|
79
|
+
: 'Informe o motivo da reprovação.'}
|
|
80
|
+
</DialogDescription>
|
|
81
|
+
</DialogHeader>
|
|
82
|
+
<div className="space-y-4">
|
|
83
|
+
<div className="space-y-2">
|
|
84
|
+
<Label htmlFor="comentario">Comentário</Label>
|
|
85
|
+
<Textarea
|
|
86
|
+
id="comentario"
|
|
87
|
+
placeholder={
|
|
88
|
+
isAprovar
|
|
89
|
+
? 'Comentário opcional...'
|
|
90
|
+
: 'Informe o motivo da reprovação...'
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<DialogFooter>
|
|
96
|
+
<Button variant="outline">Cancelar</Button>
|
|
97
|
+
<Button
|
|
98
|
+
variant={isAprovar ? 'default' : 'destructive'}
|
|
99
|
+
onClick={onConfirm}
|
|
100
|
+
>
|
|
101
|
+
{isAprovar ? 'Confirmar Aprovação' : 'Confirmar Reprovação'}
|
|
102
|
+
</Button>
|
|
103
|
+
</DialogFooter>
|
|
104
|
+
</DialogContent>
|
|
105
|
+
</Dialog>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default function AprovacoesPage() {
|
|
110
|
+
const { data } = useFinanceData();
|
|
111
|
+
const { aprovacoesPendentes, titulosPagar, pessoas } = data;
|
|
112
|
+
|
|
113
|
+
const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
|
|
114
|
+
|
|
115
|
+
const [aprovacoes, setAprovacoes] = useState(aprovacoesPendentes);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
setAprovacoes(aprovacoesPendentes);
|
|
119
|
+
}, [aprovacoesPendentes]);
|
|
120
|
+
|
|
121
|
+
const handleAprovacao = (id: string) => {
|
|
122
|
+
setAprovacoes(aprovacoes.filter((a) => a.id !== id));
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const totalPendente = aprovacoes.reduce((acc, a) => acc + a.valor, 0);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex flex-col h-screen px-4">
|
|
129
|
+
<PageHeader
|
|
130
|
+
title="Aprovações"
|
|
131
|
+
description="Gerencie as aprovações de títulos"
|
|
132
|
+
breadcrumbs={[
|
|
133
|
+
{
|
|
134
|
+
label: 'Contas a Pagar',
|
|
135
|
+
href: '/finance/accounts-payable/installments',
|
|
136
|
+
},
|
|
137
|
+
{ label: 'Aprovações' },
|
|
138
|
+
]}
|
|
139
|
+
/>
|
|
140
|
+
|
|
141
|
+
<div className="grid gap-4 md:grid-cols-3">
|
|
142
|
+
<Card>
|
|
143
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
144
|
+
<CardTitle className="text-sm font-medium">Pendentes</CardTitle>
|
|
145
|
+
<Clock className="h-4 w-4 text-muted-foreground" />
|
|
146
|
+
</CardHeader>
|
|
147
|
+
<CardContent>
|
|
148
|
+
<div className="text-2xl font-bold">{aprovacoes.length}</div>
|
|
149
|
+
</CardContent>
|
|
150
|
+
</Card>
|
|
151
|
+
<Card>
|
|
152
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
153
|
+
<CardTitle className="text-sm font-medium">Valor Total</CardTitle>
|
|
154
|
+
<AlertTriangle className="h-4 w-4 text-muted-foreground" />
|
|
155
|
+
</CardHeader>
|
|
156
|
+
<CardContent>
|
|
157
|
+
<div className="text-2xl font-bold">
|
|
158
|
+
<Money value={totalPendente} />
|
|
159
|
+
</div>
|
|
160
|
+
</CardContent>
|
|
161
|
+
</Card>
|
|
162
|
+
<Card>
|
|
163
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
164
|
+
<CardTitle className="text-sm font-medium">Urgentes</CardTitle>
|
|
165
|
+
<AlertTriangle className="h-4 w-4 text-orange-500" />
|
|
166
|
+
</CardHeader>
|
|
167
|
+
<CardContent>
|
|
168
|
+
<div className="text-2xl font-bold">
|
|
169
|
+
{
|
|
170
|
+
aprovacoes.filter(
|
|
171
|
+
(a) => a.urgencia === 'alta' || a.urgencia === 'critica'
|
|
172
|
+
).length
|
|
173
|
+
}
|
|
174
|
+
</div>
|
|
175
|
+
</CardContent>
|
|
176
|
+
</Card>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<Card>
|
|
180
|
+
<CardHeader>
|
|
181
|
+
<CardTitle>Aprovações Pendentes</CardTitle>
|
|
182
|
+
<CardDescription>Títulos aguardando sua aprovação</CardDescription>
|
|
183
|
+
</CardHeader>
|
|
184
|
+
<CardContent>
|
|
185
|
+
{aprovacoes.length > 0 ? (
|
|
186
|
+
<Table>
|
|
187
|
+
<TableHeader>
|
|
188
|
+
<TableRow>
|
|
189
|
+
<TableHead>Documento</TableHead>
|
|
190
|
+
<TableHead>Fornecedor</TableHead>
|
|
191
|
+
<TableHead>Solicitante</TableHead>
|
|
192
|
+
<TableHead className="text-right">Valor</TableHead>
|
|
193
|
+
<TableHead>Política</TableHead>
|
|
194
|
+
<TableHead>Urgência</TableHead>
|
|
195
|
+
<TableHead>Data</TableHead>
|
|
196
|
+
<TableHead className="text-right">Ações</TableHead>
|
|
197
|
+
</TableRow>
|
|
198
|
+
</TableHeader>
|
|
199
|
+
<TableBody>
|
|
200
|
+
{aprovacoes.map((aprovacao) => {
|
|
201
|
+
const titulo = titulosPagar.find(
|
|
202
|
+
(t) => t.id === aprovacao.tituloId
|
|
203
|
+
);
|
|
204
|
+
const fornecedor = titulo
|
|
205
|
+
? getPessoaById(titulo.fornecedorId)
|
|
206
|
+
: null;
|
|
207
|
+
const urgencia =
|
|
208
|
+
urgenciaConfig[
|
|
209
|
+
aprovacao.urgencia as keyof typeof urgenciaConfig
|
|
210
|
+
] || urgenciaConfig.media;
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<TableRow key={aprovacao.id}>
|
|
214
|
+
<TableCell className="font-medium">
|
|
215
|
+
{titulo?.documento}
|
|
216
|
+
</TableCell>
|
|
217
|
+
<TableCell>{fornecedor?.nome}</TableCell>
|
|
218
|
+
<TableCell>{aprovacao.solicitante}</TableCell>
|
|
219
|
+
<TableCell className="text-right">
|
|
220
|
+
<Money value={aprovacao.valor} />
|
|
221
|
+
</TableCell>
|
|
222
|
+
<TableCell>
|
|
223
|
+
<span className="text-sm text-muted-foreground">
|
|
224
|
+
{aprovacao.politica}
|
|
225
|
+
</span>
|
|
226
|
+
</TableCell>
|
|
227
|
+
<TableCell>
|
|
228
|
+
<Badge className={urgencia.className} variant="outline">
|
|
229
|
+
{urgencia.label}
|
|
230
|
+
</Badge>
|
|
231
|
+
</TableCell>
|
|
232
|
+
<TableCell>
|
|
233
|
+
{formatarData(aprovacao.dataSolicitacao)}
|
|
234
|
+
</TableCell>
|
|
235
|
+
<TableCell>
|
|
236
|
+
<div className="flex justify-end gap-2">
|
|
237
|
+
<AprovacaoDialog
|
|
238
|
+
tipo="aprovar"
|
|
239
|
+
onConfirm={() => handleAprovacao(aprovacao.id)}
|
|
240
|
+
/>
|
|
241
|
+
<AprovacaoDialog
|
|
242
|
+
tipo="reprovar"
|
|
243
|
+
onConfirm={() => handleAprovacao(aprovacao.id)}
|
|
244
|
+
/>
|
|
245
|
+
</div>
|
|
246
|
+
</TableCell>
|
|
247
|
+
</TableRow>
|
|
248
|
+
);
|
|
249
|
+
})}
|
|
250
|
+
</TableBody>
|
|
251
|
+
</Table>
|
|
252
|
+
) : (
|
|
253
|
+
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
254
|
+
<CheckCircle className="h-12 w-12 text-green-500" />
|
|
255
|
+
<h3 className="mt-4 text-lg font-semibold">Tudo em dia!</h3>
|
|
256
|
+
<p className="text-muted-foreground">
|
|
257
|
+
Não há aprovações pendentes.
|
|
258
|
+
</p>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
</CardContent>
|
|
262
|
+
</Card>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
}
|