@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.
@@ -0,0 +1,388 @@
1
+ 'use client';
2
+
3
+ import { AuditTimeline } from '@/components/ui/audit-timeline';
4
+ import { Badge } from '@/components/ui/badge';
5
+ import { Button } from '@/components/ui/button';
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from '@/components/ui/card';
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuContent,
16
+ DropdownMenuItem,
17
+ DropdownMenuSeparator,
18
+ DropdownMenuTrigger,
19
+ } from '@/components/ui/dropdown-menu';
20
+ import { Money } from '@/components/ui/money';
21
+ import { PageHeader } from '@/components/ui/page-header';
22
+ import { StatusBadge } from '@/components/ui/status-badge';
23
+ import {
24
+ Table,
25
+ TableBody,
26
+ TableCell,
27
+ TableHead,
28
+ TableHeader,
29
+ TableRow,
30
+ } from '@/components/ui/table';
31
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
32
+ import {
33
+ ArrowLeft,
34
+ CheckCircle,
35
+ Download,
36
+ Edit,
37
+ FileText,
38
+ MoreHorizontal,
39
+ Undo,
40
+ XCircle,
41
+ } from 'lucide-react';
42
+ import Link from 'next/link';
43
+ import { useParams } from 'next/navigation';
44
+ import { formatarData } from '../../../_lib/formatters';
45
+ import { useFinanceData } from '../../../_lib/use-finance-data';
46
+
47
+ export default function TituloDetalhePage() {
48
+ const params = useParams<{ id: string }>();
49
+ const id = params?.id;
50
+ const { data } = useFinanceData();
51
+ const {
52
+ titulosPagar,
53
+ pessoas,
54
+ categorias,
55
+ centrosCusto,
56
+ contasBancarias,
57
+ logsAuditoria,
58
+ tags,
59
+ } = data;
60
+
61
+ const titulo = titulosPagar.find((t) => t.id === id);
62
+
63
+ if (!titulo) {
64
+ return (
65
+ <div className="space-y-6">
66
+ <PageHeader
67
+ title="Título não encontrado"
68
+ description="Não foi possível carregar os dados do título."
69
+ breadcrumbs={[
70
+ { label: 'Contas a Pagar', href: '/finance/accounts-payable/installments' },
71
+ { label: 'Títulos e Parcelas', href: '/finance/accounts-payable/installments' },
72
+ ]}
73
+ />
74
+ </div>
75
+ );
76
+ }
77
+
78
+ const getPessoaById = (personId?: string) =>
79
+ pessoas.find((p) => p.id === personId);
80
+ const getCategoriaById = (categoryId?: string) =>
81
+ categorias.find((c) => c.id === categoryId);
82
+ const getCentroCustoById = (costCenterId?: string) =>
83
+ centrosCusto.find((c) => c.id === costCenterId);
84
+ const getContaBancariaById = (bankId?: string) =>
85
+ contasBancarias.find((c) => c.id === bankId);
86
+
87
+ const fornecedor = getPessoaById(titulo.fornecedorId);
88
+ const categoria = getCategoriaById(titulo.categoriaId);
89
+ const centroCusto = getCentroCustoById(titulo.centroCustoId);
90
+ const tituloTags = titulo.tags
91
+ .map((tagId: any) => tags.find((t: any) => t.id === tagId))
92
+ .filter(Boolean);
93
+
94
+ const auditEvents = logsAuditoria
95
+ .filter(
96
+ (log) => log.entidadeId === titulo.id && log.entidade === 'TituloPagar'
97
+ )
98
+ .map((log) => ({
99
+ id: log.id,
100
+ data: log.data,
101
+ usuarioId: log.usuarioId,
102
+ acao: log.acao,
103
+ detalhes: log.detalhes,
104
+ antes: log.antes,
105
+ depois: log.depois,
106
+ }));
107
+
108
+ return (
109
+ <div className="space-y-6">
110
+ <div className="flex items-center gap-4">
111
+ <Button variant="ghost" size="icon" asChild>
112
+ <Link href="/finance/accounts-payable/installments">
113
+ <ArrowLeft className="h-4 w-4" />
114
+ </Link>
115
+ </Button>
116
+ <PageHeader
117
+ title={titulo.documento}
118
+ description={titulo.descricao}
119
+ breadcrumbs={[
120
+ { label: 'Contas a Pagar', href: '/finance/accounts-payable/installments' },
121
+ { label: 'Títulos e Parcelas', href: '/finance/accounts-payable/installments' },
122
+ { label: titulo.documento },
123
+ ]}
124
+ actions={
125
+ <div className="flex items-center gap-2">
126
+ <StatusBadge status={titulo.status} />
127
+ <DropdownMenu>
128
+ <DropdownMenuTrigger asChild>
129
+ <Button variant="outline">
130
+ <MoreHorizontal className="mr-2 h-4 w-4" />
131
+ Ações
132
+ </Button>
133
+ </DropdownMenuTrigger>
134
+ <DropdownMenuContent align="end">
135
+ <DropdownMenuItem>
136
+ <Edit className="mr-2 h-4 w-4" />
137
+ Editar
138
+ </DropdownMenuItem>
139
+ <DropdownMenuItem>
140
+ <CheckCircle className="mr-2 h-4 w-4" />
141
+ Aprovar
142
+ </DropdownMenuItem>
143
+ <DropdownMenuItem>
144
+ <Download className="mr-2 h-4 w-4" />
145
+ Baixar
146
+ </DropdownMenuItem>
147
+ <DropdownMenuItem>
148
+ <Undo className="mr-2 h-4 w-4" />
149
+ Estornar
150
+ </DropdownMenuItem>
151
+ <DropdownMenuSeparator />
152
+ <DropdownMenuItem className="text-destructive">
153
+ <XCircle className="mr-2 h-4 w-4" />
154
+ Cancelar
155
+ </DropdownMenuItem>
156
+ </DropdownMenuContent>
157
+ </DropdownMenu>
158
+ </div>
159
+ }
160
+ />
161
+ </div>
162
+
163
+ <div className="grid gap-6 lg:grid-cols-3">
164
+ <Card className="lg:col-span-2">
165
+ <CardHeader>
166
+ <CardTitle>Dados do Documento</CardTitle>
167
+ </CardHeader>
168
+ <CardContent>
169
+ <dl className="grid gap-4 sm:grid-cols-2">
170
+ <div>
171
+ <dt className="text-sm font-medium text-muted-foreground">
172
+ Fornecedor
173
+ </dt>
174
+ <dd className="mt-1">
175
+ <Link
176
+ href={`/cadastros/pessoas/${fornecedor?.id}`}
177
+ className="hover:underline"
178
+ >
179
+ {fornecedor?.nome}
180
+ </Link>
181
+ </dd>
182
+ </div>
183
+ <div>
184
+ <dt className="text-sm font-medium text-muted-foreground">
185
+ CNPJ/CPF
186
+ </dt>
187
+ <dd className="mt-1">{fornecedor?.documento}</dd>
188
+ </div>
189
+ <div>
190
+ <dt className="text-sm font-medium text-muted-foreground">
191
+ Competência
192
+ </dt>
193
+ <dd className="mt-1">{titulo.competencia}</dd>
194
+ </div>
195
+ <div>
196
+ <dt className="text-sm font-medium text-muted-foreground">
197
+ Valor Total
198
+ </dt>
199
+ <dd className="mt-1 text-lg font-semibold">
200
+ <Money value={titulo.valorTotal} />
201
+ </dd>
202
+ </div>
203
+ <div>
204
+ <dt className="text-sm font-medium text-muted-foreground">
205
+ Categoria
206
+ </dt>
207
+ <dd className="mt-1">{categoria?.nome}</dd>
208
+ </div>
209
+ <div>
210
+ <dt className="text-sm font-medium text-muted-foreground">
211
+ Centro de Custo
212
+ </dt>
213
+ <dd className="mt-1">{centroCusto?.nome}</dd>
214
+ </div>
215
+ <div>
216
+ <dt className="text-sm font-medium text-muted-foreground">
217
+ Data de Criação
218
+ </dt>
219
+ <dd className="mt-1">{formatarData(titulo.criadoEm)}</dd>
220
+ </div>
221
+ <div>
222
+ <dt className="text-sm font-medium text-muted-foreground">
223
+ Tags
224
+ </dt>
225
+ <dd className="mt-1 flex gap-1">
226
+ {tituloTags.length > 0 ? (
227
+ tituloTags.map((tag: any) => (
228
+ <Badge
229
+ key={tag?.id}
230
+ variant="outline"
231
+ style={{ borderColor: tag?.cor, color: tag?.cor }}
232
+ >
233
+ {tag?.nome}
234
+ </Badge>
235
+ ))
236
+ ) : (
237
+ <span className="text-muted-foreground">-</span>
238
+ )}
239
+ </dd>
240
+ </div>
241
+ </dl>
242
+ </CardContent>
243
+ </Card>
244
+
245
+ <Card>
246
+ <CardHeader>
247
+ <CardTitle>Anexos</CardTitle>
248
+ <CardDescription>Documentos relacionados</CardDescription>
249
+ </CardHeader>
250
+ <CardContent>
251
+ {titulo.anexos.length > 0 ? (
252
+ <ul className="space-y-2">
253
+ {titulo.anexos.map((anexo: any, i: number) => (
254
+ <li key={i}>
255
+ <Button
256
+ variant="ghost"
257
+ className="h-auto w-full justify-start p-2"
258
+ >
259
+ <FileText className="mr-2 h-4 w-4" />
260
+ {anexo}
261
+ </Button>
262
+ </li>
263
+ ))}
264
+ </ul>
265
+ ) : (
266
+ <p className="text-sm text-muted-foreground">Nenhum anexo</p>
267
+ )}
268
+ </CardContent>
269
+ </Card>
270
+ </div>
271
+
272
+ <Tabs defaultValue="parcelas">
273
+ <TabsList>
274
+ <TabsTrigger value="parcelas">Parcelas</TabsTrigger>
275
+ <TabsTrigger value="liquidacoes">Liquidações</TabsTrigger>
276
+ <TabsTrigger value="auditoria">Auditoria</TabsTrigger>
277
+ </TabsList>
278
+
279
+ <TabsContent value="parcelas" className="mt-4">
280
+ <Card>
281
+ <CardContent className="pt-6">
282
+ <Table>
283
+ <TableHeader>
284
+ <TableRow>
285
+ <TableHead>Parcela</TableHead>
286
+ <TableHead>Vencimento</TableHead>
287
+ <TableHead className="text-right">Valor</TableHead>
288
+ <TableHead>Método</TableHead>
289
+ <TableHead>Status</TableHead>
290
+ </TableRow>
291
+ </TableHeader>
292
+ <TableBody>
293
+ {titulo.parcelas.map((parcela: any) => (
294
+ <TableRow key={parcela.id}>
295
+ <TableCell>
296
+ {parcela.numero}/{titulo.parcelas.length}
297
+ </TableCell>
298
+ <TableCell>{formatarData(parcela.vencimento)}</TableCell>
299
+ <TableCell className="text-right">
300
+ <Money value={parcela.valor} />
301
+ </TableCell>
302
+ <TableCell className="capitalize">
303
+ {parcela.metodoPagamento}
304
+ </TableCell>
305
+ <TableCell>
306
+ <StatusBadge status={parcela.status} />
307
+ </TableCell>
308
+ </TableRow>
309
+ ))}
310
+ </TableBody>
311
+ </Table>
312
+ </CardContent>
313
+ </Card>
314
+ </TabsContent>
315
+
316
+ <TabsContent value="liquidacoes" className="mt-4">
317
+ <Card>
318
+ <CardContent className="pt-6">
319
+ {titulo.parcelas.some((p: any) => p.liquidacoes.length > 0) ? (
320
+ <Table>
321
+ <TableHeader>
322
+ <TableRow>
323
+ <TableHead>Data</TableHead>
324
+ <TableHead className="text-right">Valor</TableHead>
325
+ <TableHead className="text-right">Juros</TableHead>
326
+ <TableHead className="text-right">Desconto</TableHead>
327
+ <TableHead className="text-right">Multa</TableHead>
328
+ <TableHead>Conta</TableHead>
329
+ <TableHead>Método</TableHead>
330
+ </TableRow>
331
+ </TableHeader>
332
+ <TableBody>
333
+ {titulo.parcelas.flatMap((parcela: any) =>
334
+ parcela.liquidacoes.map((liq: any) => {
335
+ const conta = getContaBancariaById(liq.contaBancariaId);
336
+ return (
337
+ <TableRow key={liq.id}>
338
+ <TableCell>{formatarData(liq.data)}</TableCell>
339
+ <TableCell className="text-right">
340
+ <Money value={liq.valor} />
341
+ </TableCell>
342
+ <TableCell className="text-right">
343
+ <Money value={liq.juros} />
344
+ </TableCell>
345
+ <TableCell className="text-right">
346
+ <Money value={liq.desconto} />
347
+ </TableCell>
348
+ <TableCell className="text-right">
349
+ <Money value={liq.multa} />
350
+ </TableCell>
351
+ <TableCell>{conta?.descricao}</TableCell>
352
+ <TableCell className="capitalize">
353
+ {liq.metodo}
354
+ </TableCell>
355
+ </TableRow>
356
+ );
357
+ })
358
+ )}
359
+ </TableBody>
360
+ </Table>
361
+ ) : (
362
+ <p className="text-center text-muted-foreground py-8">
363
+ Nenhuma liquidação registrada
364
+ </p>
365
+ )}
366
+ </CardContent>
367
+ </Card>
368
+ </TabsContent>
369
+
370
+ <TabsContent value="auditoria" className="mt-4">
371
+ <Card>
372
+ <CardContent className="pt-6">
373
+ {auditEvents.length > 0 ? (
374
+ <AuditTimeline events={auditEvents} />
375
+ ) : (
376
+ <p className="text-center text-muted-foreground py-8">
377
+ Nenhum evento de auditoria
378
+ </p>
379
+ )}
380
+ </CardContent>
381
+ </Card>
382
+ </TabsContent>
383
+ </Tabs>
384
+ </div>
385
+ );
386
+ }
387
+
388
+