@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,364 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@/components/ui/button';
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuSeparator,
9
+ DropdownMenuTrigger,
10
+ } from '@/components/ui/dropdown-menu';
11
+ import { FilterBar } from '@/components/ui/filter-bar';
12
+ import { Input } from '@/components/ui/input';
13
+ import { Label } from '@/components/ui/label';
14
+ import { Money } from '@/components/ui/money';
15
+ import { PageHeader } from '@/components/ui/page-header';
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectItem,
20
+ SelectTrigger,
21
+ SelectValue,
22
+ } from '@/components/ui/select';
23
+ import {
24
+ Sheet,
25
+ SheetContent,
26
+ SheetDescription,
27
+ SheetHeader,
28
+ SheetTitle,
29
+ SheetTrigger,
30
+ } from '@/components/ui/sheet';
31
+ import { StatusBadge } from '@/components/ui/status-badge';
32
+ import {
33
+ Table,
34
+ TableBody,
35
+ TableCell,
36
+ TableHead,
37
+ TableHeader,
38
+ TableRow,
39
+ } from '@/components/ui/table';
40
+ import { Textarea } from '@/components/ui/textarea';
41
+ import {
42
+ CheckCircle,
43
+ Download,
44
+ Edit,
45
+ Eye,
46
+ MoreHorizontal,
47
+ Paperclip,
48
+ Plus,
49
+ Undo,
50
+ XCircle,
51
+ } from 'lucide-react';
52
+ import Link from 'next/link';
53
+ import { useState } from 'react';
54
+ import { formatarData } from '../../_lib/formatters';
55
+ import { useFinanceData } from '../../_lib/use-finance-data';
56
+
57
+ function NovoTituloSheet({
58
+ pessoas,
59
+ categorias,
60
+ centrosCusto,
61
+ }: {
62
+ pessoas: any[];
63
+ categorias: any[];
64
+ centrosCusto: any[];
65
+ }) {
66
+ return (
67
+ <Sheet>
68
+ <SheetTrigger asChild>
69
+ <Button>
70
+ <Plus className="mr-2 h-4 w-4" />
71
+ Novo Título
72
+ </Button>
73
+ </SheetTrigger>
74
+ <SheetContent className="w-full sm:max-w-lg overflow-y-auto">
75
+ <SheetHeader>
76
+ <SheetTitle>Novo Título a Pagar</SheetTitle>
77
+ <SheetDescription>
78
+ Preencha os dados para criar um novo título a pagar.
79
+ </SheetDescription>
80
+ </SheetHeader>
81
+ <form className="mt-6 space-y-4">
82
+ <div className="grid gap-4">
83
+ <div className="space-y-2">
84
+ <Label htmlFor="documento">Documento</Label>
85
+ <Input id="documento" placeholder="NF-00000" />
86
+ </div>
87
+ <div className="space-y-2">
88
+ <Label htmlFor="fornecedor">Fornecedor</Label>
89
+ <Select>
90
+ <SelectTrigger>
91
+ <SelectValue placeholder="Selecione..." />
92
+ </SelectTrigger>
93
+ <SelectContent>
94
+ {pessoas
95
+ .filter(
96
+ (p) => p.tipo === 'fornecedor' || p.tipo === 'ambos'
97
+ )
98
+ .map((p) => (
99
+ <SelectItem key={p.id} value={p.id}>
100
+ {p.nome}
101
+ </SelectItem>
102
+ ))}
103
+ </SelectContent>
104
+ </Select>
105
+ </div>
106
+ <div className="grid grid-cols-2 gap-4">
107
+ <div className="space-y-2">
108
+ <Label htmlFor="competencia">Competência</Label>
109
+ <Input id="competencia" type="month" />
110
+ </div>
111
+ <div className="space-y-2">
112
+ <Label htmlFor="vencimento">Vencimento</Label>
113
+ <Input id="vencimento" type="date" />
114
+ </div>
115
+ </div>
116
+ <div className="space-y-2">
117
+ <Label htmlFor="valor">Valor Total</Label>
118
+ <Input id="valor" type="number" placeholder="0,00" step="0.01" />
119
+ </div>
120
+ <div className="space-y-2">
121
+ <Label htmlFor="categoria">Categoria</Label>
122
+ <Select>
123
+ <SelectTrigger>
124
+ <SelectValue placeholder="Selecione..." />
125
+ </SelectTrigger>
126
+ <SelectContent>
127
+ {categorias
128
+ .filter((c) => c.natureza === 'despesa')
129
+ .map((c) => (
130
+ <SelectItem key={c.id} value={c.id}>
131
+ {c.codigo} - {c.nome}
132
+ </SelectItem>
133
+ ))}
134
+ </SelectContent>
135
+ </Select>
136
+ </div>
137
+ <div className="space-y-2">
138
+ <Label htmlFor="centroCusto">Centro de Custo</Label>
139
+ <Select>
140
+ <SelectTrigger>
141
+ <SelectValue placeholder="Selecione..." />
142
+ </SelectTrigger>
143
+ <SelectContent>
144
+ {centrosCusto.map((c) => (
145
+ <SelectItem key={c.id} value={c.id}>
146
+ {c.codigo} - {c.nome}
147
+ </SelectItem>
148
+ ))}
149
+ </SelectContent>
150
+ </Select>
151
+ </div>
152
+ <div className="space-y-2">
153
+ <Label htmlFor="metodo">Forma de Pagamento</Label>
154
+ <Select>
155
+ <SelectTrigger>
156
+ <SelectValue placeholder="Selecione..." />
157
+ </SelectTrigger>
158
+ <SelectContent>
159
+ <SelectItem value="boleto">Boleto</SelectItem>
160
+ <SelectItem value="pix">PIX</SelectItem>
161
+ <SelectItem value="transferencia">Transferência</SelectItem>
162
+ <SelectItem value="cartao">Cartão</SelectItem>
163
+ <SelectItem value="dinheiro">Dinheiro</SelectItem>
164
+ <SelectItem value="cheque">Cheque</SelectItem>
165
+ </SelectContent>
166
+ </Select>
167
+ </div>
168
+ <div className="space-y-2">
169
+ <Label htmlFor="descricao">Descrição</Label>
170
+ <Textarea id="descricao" placeholder="Descrição do título..." />
171
+ </div>
172
+ </div>
173
+ <div className="flex justify-end gap-2 pt-4">
174
+ <Button type="button" variant="outline">
175
+ Cancelar
176
+ </Button>
177
+ <Button type="submit">Salvar</Button>
178
+ </div>
179
+ </form>
180
+ </SheetContent>
181
+ </Sheet>
182
+ );
183
+ }
184
+
185
+ export default function TitulosPagarPage() {
186
+ const { data } = useFinanceData();
187
+ const { titulosPagar, pessoas, categorias, centrosCusto } = data;
188
+
189
+ const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
190
+ const getCategoriaById = (id?: string) => categorias.find((c) => c.id === id);
191
+
192
+ const [search, setSearch] = useState('');
193
+ const [statusFilter, setStatusFilter] = useState<string>('');
194
+
195
+ const filteredTitulos = titulosPagar.filter((titulo) => {
196
+ const matchesSearch =
197
+ titulo.documento.toLowerCase().includes(search.toLowerCase()) ||
198
+ getPessoaById(titulo.fornecedorId)
199
+ ?.nome.toLowerCase()
200
+ .includes(search.toLowerCase());
201
+
202
+ const matchesStatus = !statusFilter || titulo.status === statusFilter;
203
+
204
+ return matchesSearch && matchesStatus;
205
+ });
206
+
207
+ return (
208
+ <div className="space-y-6">
209
+ <PageHeader
210
+ title="Títulos a Pagar"
211
+ description="Gerencie suas contas a pagar"
212
+ breadcrumbs={[
213
+ { label: 'Contas a Pagar', href: '/finance/accounts-payable/installments' },
214
+ { label: 'Títulos e Parcelas' },
215
+ ]}
216
+ actions={
217
+ <NovoTituloSheet
218
+ pessoas={pessoas}
219
+ categorias={categorias}
220
+ centrosCusto={centrosCusto}
221
+ />
222
+ }
223
+ />
224
+
225
+ <FilterBar
226
+ searchPlaceholder="Buscar por documento ou fornecedor..."
227
+ searchValue={search}
228
+ onSearchChange={setSearch}
229
+ filters={[
230
+ {
231
+ id: 'status',
232
+ label: 'Status',
233
+ value: statusFilter,
234
+ onChange: setStatusFilter,
235
+ options: [
236
+ { value: 'all', label: 'Todos' },
237
+ { value: 'rascunho', label: 'Rascunho' },
238
+ { value: 'aprovado', label: 'Aprovado' },
239
+ { value: 'aberto', label: 'Aberto' },
240
+ { value: 'parcial', label: 'Parcial' },
241
+ { value: 'liquidado', label: 'Liquidado' },
242
+ { value: 'vencido', label: 'Vencido' },
243
+ { value: 'cancelado', label: 'Cancelado' },
244
+ ],
245
+ },
246
+ ]}
247
+ activeFilters={statusFilter && statusFilter !== 'all' ? 1 : 0}
248
+ onClearFilters={() => setStatusFilter('')}
249
+ />
250
+
251
+ <div className="rounded-md border">
252
+ <Table>
253
+ <TableHeader>
254
+ <TableRow>
255
+ <TableHead>Documento</TableHead>
256
+ <TableHead>Fornecedor</TableHead>
257
+ <TableHead>Competência</TableHead>
258
+ <TableHead>Vencimento</TableHead>
259
+ <TableHead className="text-right">Valor</TableHead>
260
+ <TableHead>Categoria</TableHead>
261
+ <TableHead>Status</TableHead>
262
+ <TableHead className="w-[50px]" />
263
+ </TableRow>
264
+ </TableHeader>
265
+ <TableBody>
266
+ {filteredTitulos.map((titulo) => {
267
+ const fornecedor = getPessoaById(titulo.fornecedorId);
268
+ const categoria = getCategoriaById(titulo.categoriaId);
269
+ const proximaParcela = titulo.parcelas.find(
270
+ (p: any) => p.status === 'aberto' || p.status === 'vencido'
271
+ );
272
+
273
+ return (
274
+ <TableRow key={titulo.id}>
275
+ <TableCell className="font-medium">
276
+ <Link
277
+ href={`/finance/accounts-payable/installments/${titulo.id}`}
278
+ className="hover:underline"
279
+ >
280
+ {titulo.documento}
281
+ </Link>
282
+ {titulo.anexos.length > 0 && (
283
+ <Paperclip className="ml-1 inline h-3 w-3 text-muted-foreground" />
284
+ )}
285
+ </TableCell>
286
+ <TableCell>{fornecedor?.nome}</TableCell>
287
+ <TableCell>{titulo.competencia}</TableCell>
288
+ <TableCell>
289
+ {proximaParcela
290
+ ? formatarData(proximaParcela.vencimento)
291
+ : '-'}
292
+ </TableCell>
293
+ <TableCell className="text-right">
294
+ <Money value={titulo.valorTotal} />
295
+ </TableCell>
296
+ <TableCell>{categoria?.nome}</TableCell>
297
+ <TableCell>
298
+ <StatusBadge status={titulo.status} />
299
+ </TableCell>
300
+ <TableCell>
301
+ <DropdownMenu>
302
+ <DropdownMenuTrigger asChild>
303
+ <Button variant="ghost" size="icon">
304
+ <MoreHorizontal className="h-4 w-4" />
305
+ <span className="sr-only">Ações</span>
306
+ </Button>
307
+ </DropdownMenuTrigger>
308
+ <DropdownMenuContent align="end">
309
+ <DropdownMenuItem asChild>
310
+ <Link href={`/finance/accounts-payable/installments/${titulo.id}`}>
311
+ <Eye className="mr-2 h-4 w-4" />
312
+ Ver Detalhes
313
+ </Link>
314
+ </DropdownMenuItem>
315
+ <DropdownMenuItem>
316
+ <Edit className="mr-2 h-4 w-4" />
317
+ Editar
318
+ </DropdownMenuItem>
319
+ <DropdownMenuSeparator />
320
+ <DropdownMenuItem>
321
+ <CheckCircle className="mr-2 h-4 w-4" />
322
+ Aprovar
323
+ </DropdownMenuItem>
324
+ <DropdownMenuItem>
325
+ <Download className="mr-2 h-4 w-4" />
326
+ Baixar
327
+ </DropdownMenuItem>
328
+ <DropdownMenuItem>
329
+ <Undo className="mr-2 h-4 w-4" />
330
+ Estornar
331
+ </DropdownMenuItem>
332
+ <DropdownMenuSeparator />
333
+ <DropdownMenuItem className="text-destructive">
334
+ <XCircle className="mr-2 h-4 w-4" />
335
+ Cancelar
336
+ </DropdownMenuItem>
337
+ </DropdownMenuContent>
338
+ </DropdownMenu>
339
+ </TableCell>
340
+ </TableRow>
341
+ );
342
+ })}
343
+ </TableBody>
344
+ </Table>
345
+ </div>
346
+
347
+ <div className="flex items-center justify-between">
348
+ <p className="text-sm text-muted-foreground">
349
+ Mostrando {filteredTitulos.length} de {titulosPagar.length} registros
350
+ </p>
351
+ <div className="flex items-center gap-2">
352
+ <Button variant="outline" size="sm" disabled>
353
+ Anterior
354
+ </Button>
355
+ <Button variant="outline" size="sm" disabled>
356
+ Próximo
357
+ </Button>
358
+ </div>
359
+ </div>
360
+ </div>
361
+ );
362
+ }
363
+
364
+