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