refactor(frontend): type legacy payments and catalog flows

This commit is contained in:
Tiago Yamamoto 2026-03-07 07:56:20 -06:00
parent b633939d4e
commit 48a54a616e
8 changed files with 279 additions and 285 deletions

View file

@ -1,4 +1,3 @@
// @ts-nocheck
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -11,11 +10,12 @@ import PagamentoForm from '@/components/PagamentoForm';
import PagamentoList from '@/components/PagamentoList'; import PagamentoList from '@/components/PagamentoList';
import { usePagamentos } from '@/hooks/usePagamentos'; import { usePagamentos } from '@/hooks/usePagamentos';
import { PagamentoData } from '@/services/pagamentoService'; import { PagamentoData } from '@/services/pagamentoService';
import { PagamentoDocument } from '@/types/legacyEntities';
const GestaoPagamentos = () => { const GestaoPagamentos = () => {
const router = useRouter(); const router = useRouter();
const [user, setUser] = useState<Models.User<Models.Preferences> | null>(null); const [user, setUser] = useState<Models.User<Models.Preferences> | null>(null);
const [editing, setEditing] = useState<Models.Document | null>(null); const [editing, setEditing] = useState<PagamentoDocument | null>(null);
const [activeTab, setActiveTab] = useState<'lista' | 'cadastro'>('lista'); const [activeTab, setActiveTab] = useState<'lista' | 'cadastro'>('lista');
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
@ -41,7 +41,7 @@ const GestaoPagamentos = () => {
await listarPagamentos(1, searchTerm); await listarPagamentos(1, searchTerm);
} }
} catch (error) { } catch (error) {
console.error('Usuário não autenticado:', error); console.error('Usuário não autenticado:', error);
router.push('/'); router.push('/');
} }
}; };
@ -57,17 +57,17 @@ const GestaoPagamentos = () => {
setActiveTab('lista'); setActiveTab('lista');
} }
return success; return success;
} else {
const success = await cadastrarPagamento(formData);
if (success) {
setTimeout(() => setActiveTab('lista'), 2000);
}
return success;
} }
const success = await cadastrarPagamento(formData);
if (success) {
setTimeout(() => setActiveTab('lista'), 2000);
}
return success;
}; };
const handleEdit = (pag: Models.Document) => { const handleEdit = (pagamento: PagamentoDocument) => {
setEditing(pag); setEditing(pagamento);
setActiveTab('cadastro'); setActiveTab('cadastro');
}; };
@ -99,7 +99,7 @@ const GestaoPagamentos = () => {
<div className="min-h-screen bg-gray-50 flex items-center justify-center"> <div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Verificando autenticação...</p> <p className="text-gray-600">Verificando autenticação...</p>
</div> </div>
</div> </div>
); );
@ -109,7 +109,7 @@ const GestaoPagamentos = () => {
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
<Header <Header
user={user} user={user}
title="Gestão de Pagamentos" title="Gestão de Pagamentos"
subtitle="Gerencie os pagamentos da plataforma" subtitle="Gerencie os pagamentos da plataforma"
/> />
@ -178,4 +178,3 @@ const GestaoPagamentos = () => {
}; };
export default GestaoPagamentos; export default GestaoPagamentos;

View file

@ -1,20 +1,20 @@
import React from "react"; import React from 'react';
import { Models } from "@/lib/appwrite"; import TableActions from './TableActions';
import TableActions from "./TableActions"; import { CatalogoProdutoDocument } from '@/types/legacyEntities';
interface CatalogoProdutosListProps { interface CatalogoProdutosListProps {
produtos: Models.Document[]; produtos: CatalogoProdutoDocument[];
loading: boolean; loading: boolean;
isChangingPage?: boolean; isChangingPage?: boolean;
totalProdutos: number; totalProdutos: number;
currentPage: number; currentPage: number;
pageSize: number; pageSize: number;
onEdit: (produto: Models.Document) => void; onEdit: (produto: CatalogoProdutoDocument) => void;
onDelete: (id: string) => void; onDelete: (id: string) => void;
onView: (produto: Models.Document) => void; onView: (produto: CatalogoProdutoDocument) => void;
onPrevPage: () => void; onPrevPage: () => void;
onNextPage: () => void; onNextPage: () => void;
onRowClick?: (produto: Models.Document) => void; onRowClick?: (produto: CatalogoProdutoDocument) => void;
} }
const CatalogoProdutosList: React.FC<CatalogoProdutosListProps> = ({ const CatalogoProdutosList: React.FC<CatalogoProdutosListProps> = ({
@ -42,7 +42,7 @@ const CatalogoProdutosList: React.FC<CatalogoProdutosListProps> = ({
<div className="flex items-center justify-center p-8"> <div className="flex items-center justify-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-2 text-gray-600"> <span className="ml-2 text-gray-600">
{isChangingPage ? "Carregando página..." : "Carregando produtos..."} {isChangingPage ? 'Carregando página...' : 'Carregando produtos...'}
</span> </span>
</div> </div>
</div> </div>
@ -67,36 +67,17 @@ const CatalogoProdutosList: React.FC<CatalogoProdutosListProps> = ({
<table className="laboratory-table w-full"> <table className="laboratory-table w-full">
<thead> <thead>
<tr> <tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">Nome do Produto</th>
Nome do Produto <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">Código Interno</th>
</th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">Código EAN</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">Descrição</th>
Código Interno <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">Preço Atual</th>
</th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">Ações</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">
Código EAN
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">
Descrição
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">
Preço Atual
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-50/50">
Ações
</th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{produtos.map((produto) => { {produtos.map(produto => {
const produtoData = produto as any; const nomeProduto = produto.nome || produto.descricao || 'N/A';
const catalogoProduto = produtoData["catalogo-produtos"];
const nomeProduto =
produtoData.nome ||
catalogoProduto?.nome ||
produtoData.descricao ||
catalogoProduto?.descricao ||
"N/A";
return ( return (
<tr <tr
@ -105,19 +86,11 @@ const CatalogoProdutosList: React.FC<CatalogoProdutosListProps> = ({
onClick={() => onRowClick?.(produto)} onClick={() => onRowClick?.(produto)}
> >
<td className="px-6 py-4 max-w-xs font-medium text-gray-900 line-clamp-2">{nomeProduto}</td> <td className="px-6 py-4 max-w-xs font-medium text-gray-900 line-clamp-2">{nomeProduto}</td>
<td className="px-6 py-4 text-sm text-gray-700">{produto['codigo-interno'] || 'N/A'}</td>
<td className="px-6 py-4 text-sm text-gray-700">{produto['codigo-ean'] || 'N/A'}</td>
<td className="px-6 py-4 max-w-xs text-sm text-gray-700 line-clamp-2">{produto.descricao || 'N/A'}</td>
<td className="px-6 py-4 text-sm text-gray-700"> <td className="px-6 py-4 text-sm text-gray-700">
{produtoData["codigo-interno"] || catalogoProduto?.["codigo-interno"] || "N/A"} {typeof produto['preco-atual'] === 'number' ? `R$ ${produto['preco-atual'].toFixed(2)}` : 'N/A'}
</td>
<td className="px-6 py-4 text-sm text-gray-700">
{produtoData["codigo-ean"] || catalogoProduto?.["codigo-ean"] || "N/A"}
</td>
<td className="px-6 py-4 max-w-xs text-sm text-gray-700 line-clamp-2">
{produtoData.descricao || catalogoProduto?.descricao || "N/A"}
</td>
<td className="px-6 py-4 text-sm text-gray-700">
{typeof produtoData["preco-atual"] === "number"
? `R$ ${produtoData["preco-atual"].toFixed(2)}`
: "N/A"}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<TableActions <TableActions
@ -143,25 +116,23 @@ const CatalogoProdutosList: React.FC<CatalogoProdutosListProps> = ({
disabled={currentPage === 1} disabled={currentPage === 1}
className={`px-3 py-1 rounded-md text-sm font-medium ${ className={`px-3 py-1 rounded-md text-sm font-medium ${
currentPage === 1 currentPage === 1
? "bg-gray-100 text-gray-400 cursor-not-allowed" ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: "bg-white text-gray-700 hover:bg-gray-50 border border-gray-300" : 'bg-white text-gray-700 hover:bg-gray-50 border border-gray-300'
}`} }`}
> >
Anterior Anterior
</button> </button>
<span className="px-3 py-1 text-sm text-gray-700"> <span className="px-3 py-1 text-sm text-gray-700">Página {currentPage} de {totalPages}</span>
Página {currentPage} de {totalPages}
</span>
<button <button
onClick={onNextPage} onClick={onNextPage}
disabled={currentPage === totalPages} disabled={currentPage === totalPages}
className={`px-3 py-1 rounded-md text-sm font-medium ${ className={`px-3 py-1 rounded-md text-sm font-medium ${
currentPage === totalPages currentPage === totalPages
? "bg-gray-100 text-gray-400 cursor-not-allowed" ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: "bg-white text-gray-700 hover:bg-gray-50 border border-gray-300" : 'bg-white text-gray-700 hover:bg-gray-50 border border-gray-300'
}`} }`}
> >
Próxima Próxima
</button> </button>
</div> </div>
</div> </div>
@ -171,4 +142,3 @@ const CatalogoProdutosList: React.FC<CatalogoProdutosListProps> = ({
}; };
export default CatalogoProdutosList; export default CatalogoProdutosList;

View file

@ -1,86 +1,112 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Models } from '@/lib/appwrite'; import type { PagamentoData } from '@/services/pagamentoService';
import { PagamentoDocument } from '@/types/legacyEntities';
// Interface para dados do pagamento
interface PagamentoData {
pedidos: string;
status: 'pendente' | 'pago' | 'cancelado';
metodo: 'pix' | 'cartao';
valor: number;
}
interface PagamentoFormProps { interface PagamentoFormProps {
onSubmit: (data: PagamentoData) => Promise<boolean>; onSubmit: (data: PagamentoData) => Promise<boolean>;
onCancel?: () => void; onCancel?: () => void;
initialData?: PagamentoData | null; initialData?: PagamentoDocument | null;
loading?: boolean; loading?: boolean;
} }
const statusOptions = ['pendente', 'pago', 'cancelado']; const statusOptions: PagamentoData['status'][] = ['pendente', 'pago', 'cancelado'];
const metodoOptions = ['pix', 'cartao']; const metodoOptions: PagamentoData['metodo'][] = ['pix', 'cartao'];
const normalizePedidos = (pedidos: PagamentoDocument['pedidos']): string[] => {
if (Array.isArray(pedidos)) {
return pedidos.map(item => String(item));
}
if (pedidos && typeof pedidos === 'object') {
return pedidos.$id ? [pedidos.$id] : [];
}
if (typeof pedidos === 'string' && pedidos.trim()) {
return [pedidos.trim()];
}
return [];
};
const PagamentoForm: React.FC<PagamentoFormProps> = ({ const PagamentoForm: React.FC<PagamentoFormProps> = ({
onSubmit, onSubmit,
onCancel, onCancel,
initialData, initialData,
loading = false loading = false,
}) => { }) => {
const [formData, setFormData] = useState<PagamentoData>({ const [formData, setFormData] = useState<PagamentoData>({
pedidos: '', pedidos: [],
status: 'pendente', status: 'pendente',
metodo: 'pix', metodo: 'pix',
valor: 0 valor: 0,
}); });
const [pedidoInput, setPedidoInput] = useState('');
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
const pedidos = normalizePedidos(initialData.pedidos);
setFormData({ setFormData({
pedidos: initialData.pedidos || '', pedidos,
status: initialData.status || 'pendente', status: initialData.status || 'pendente',
metodo: initialData.metodo || 'pix', metodo: initialData.metodo || 'pix',
valor: Number(initialData.valor) || 0 valor: Number(initialData.valor) || 0,
}); });
} else { setPedidoInput(pedidos.join(', '));
setFormData({ pedidos: '', status: 'pendente', metodo: 'pix', valor: 0 }); return;
} }
setFormData({ pedidos: [], status: 'pendente', metodo: 'pix', valor: 0 });
setPedidoInput('');
}, [initialData]); }, [initialData]);
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const pedidos = pedidoInput
.split(',')
.map(item => item.trim())
.filter(Boolean);
const success = await onSubmit(formData); const success = await onSubmit({ ...formData, pedidos });
if (success) { if (success) {
setMessage({ setMessage({
type: 'success', type: 'success',
text: initialData ? '🔄 Pagamento atualizado com sucesso!' : '🎉 Pagamento cadastrado com sucesso!' text: initialData ? 'Pagamento atualizado com sucesso!' : 'Pagamento cadastrado com sucesso!',
}); });
if (!initialData) { if (!initialData) {
setFormData({ pedidos: '', status: 'pendente', metodo: 'pix', valor: 0 }); setFormData({ pedidos: [], status: 'pendente', metodo: 'pix', valor: 0 });
setPedidoInput('');
} }
setTimeout(() => setMessage(null), 3000); setTimeout(() => setMessage(null), 3000);
} else { return;
setMessage({ }
type: 'error',
text: initialData ? 'Erro ao atualizar pagamento' : 'Erro ao cadastrar pagamento' setMessage({
}); type: 'error',
text: initialData ? 'Erro ao atualizar pagamento' : 'Erro ao cadastrar pagamento',
});
};
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
if (message) {
setMessage(null);
} }
}; };
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => { const handleValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target; setFormData(prev => ({ ...prev, valor: Number(e.target.value) }));
setFormData((prev: PagamentoData) => ({ ...prev, [name]: name === 'valor' ? Number(value) : value })); if (message) {
if (message) setMessage(null); setMessage(null);
}
}; };
return ( return (
<div className="bg-white rounded-lg shadow-md p-6"> <div className="bg-white rounded-lg shadow-md p-6">
<div className="mb-6"> <div className="mb-6">
<h2 className="text-2xl font-bold text-gray-900 mb-2"> <h2 className="text-2xl font-bold text-gray-900 mb-2">
{initialData ? '✏️ Editar Pagamento' : 'Novo Pagamento'} {initialData ? 'Editar Pagamento' : 'Novo Pagamento'}
</h2> </h2>
<p className="text-gray-600"> <p className="text-gray-600">
{initialData ? 'Atualize os dados do pagamento.' : 'Cadastre um novo pagamento.'} {initialData ? 'Atualize os dados do pagamento.' : 'Cadastre um novo pagamento.'}
@ -105,12 +131,12 @@ const PagamentoForm: React.FC<PagamentoFormProps> = ({
type="text" type="text"
id="pedidos" id="pedidos"
name="pedidos" name="pedidos"
value={formData.pedidos} value={pedidoInput}
onChange={handleInputChange} onChange={e => setPedidoInput(e.target.value)}
required required
disabled={loading} disabled={loading}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed" className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
placeholder="ID do pedido" placeholder="ID do pedido ou lista separada por vírgula"
/> />
</div> </div>
@ -122,7 +148,7 @@ const PagamentoForm: React.FC<PagamentoFormProps> = ({
id="status" id="status"
name="status" name="status"
value={formData.status} value={formData.status}
onChange={handleInputChange} onChange={handleSelectChange}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed" className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
disabled={loading} disabled={loading}
> >
@ -134,13 +160,13 @@ const PagamentoForm: React.FC<PagamentoFormProps> = ({
<div> <div>
<label htmlFor="metodo" className="block text-sm font-medium text-gray-700 mb-2"> <label htmlFor="metodo" className="block text-sm font-medium text-gray-700 mb-2">
Método * Método *
</label> </label>
<select <select
id="metodo" id="metodo"
name="metodo" name="metodo"
value={formData.metodo} value={formData.metodo}
onChange={handleInputChange} onChange={handleSelectChange}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed" className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
disabled={loading} disabled={loading}
> >
@ -159,8 +185,8 @@ const PagamentoForm: React.FC<PagamentoFormProps> = ({
step="0.01" step="0.01"
id="valor" id="valor"
name="valor" name="valor"
value={formData.valor} value={formData.valor || 0}
onChange={handleInputChange} onChange={handleValueChange}
required required
disabled={loading} disabled={loading}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed" className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
@ -175,7 +201,7 @@ const PagamentoForm: React.FC<PagamentoFormProps> = ({
disabled={loading} disabled={loading}
className="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors cursor-pointer" className="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors cursor-pointer"
> >
{loading ? '⏳ Processando...' : (initialData ? '🔄 Atualizar' : '➕ Cadastrar')} {loading ? 'Processando...' : (initialData ? 'Atualizar' : 'Cadastrar')}
</button> </button>
{onCancel && ( {onCancel && (
@ -185,7 +211,7 @@ const PagamentoForm: React.FC<PagamentoFormProps> = ({
disabled={loading} disabled={loading}
className="flex-1 bg-gray-600 text-white py-2 px-4 rounded-md hover:bg-gray-700 focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors cursor-pointer" className="flex-1 bg-gray-600 text-white py-2 px-4 rounded-md hover:bg-gray-700 focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors cursor-pointer"
> >
❌ Cancelar Cancelar
</button> </button>
)} )}
</div> </div>

View file

@ -1,20 +1,20 @@
import React from 'react'; import React from 'react';
import { Models } from '@/lib/appwrite';
import RefreshButton from './RefreshButton'; import RefreshButton from './RefreshButton';
import ListHeader from './ListHeader'; import ListHeader from './ListHeader';
import DataTable, { Column } from './DataTable'; import DataTable, { Column } from './DataTable';
import Pagination from './Pagination'; import Pagination from './Pagination';
import TableActions from './TableActions'; import TableActions from './TableActions';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
import { PagamentoDocument } from '@/types/legacyEntities';
interface PagamentoListProps { interface PagamentoListProps {
pagamentos: Models.Document[]; pagamentos: PagamentoDocument[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
totalPagamentos: number; totalPagamentos: number;
currentPage: number; currentPage: number;
pageSize: number; pageSize: number;
onEdit: (p: Models.Document) => void; onEdit: (pagamento: PagamentoDocument) => void;
onDelete: (id: string) => Promise<boolean>; onDelete: (id: string) => Promise<boolean>;
onRefresh: () => void; onRefresh: () => void;
onPrevPage: () => void; onPrevPage: () => void;
@ -22,11 +22,6 @@ interface PagamentoListProps {
onSearch: (term: string) => void; onSearch: (term: string) => void;
} }
type PagamentoDocument = Models.Document & {
pedidos?: string[] | { $id?: string } | string | null;
valor?: number | string;
};
const PagamentoList: React.FC<PagamentoListProps> = ({ const PagamentoList: React.FC<PagamentoListProps> = ({
pagamentos, pagamentos,
loading, loading,
@ -52,18 +47,28 @@ const PagamentoList: React.FC<PagamentoListProps> = ({
header: 'Pedido', header: 'Pedido',
render: row => { render: row => {
const pedidos = row.pedidos; const pedidos = row.pedidos;
if (Array.isArray(pedidos)) return pedidos.join(', '); if (Array.isArray(pedidos)) {
if (pedidos && typeof pedidos === 'object') return pedidos.$id || 'N/A'; return pedidos.join(', ');
}
if (pedidos && typeof pedidos === 'object') {
return pedidos.$id || 'N/A';
}
return pedidos ?? 'N/A'; return pedidos ?? 'N/A';
}, },
}, },
{ key: 'status', header: 'Status' }, { key: 'status', header: 'Status' },
{ key: 'metodo', header: 'Método' }, { key: 'metodo', header: 'Método' },
{ key: 'valor', header: 'Valor', render: row => {
Number(row.valor).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }) }, key: 'valor',
header: 'Valor',
render: row => Number(row.valor || 0).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }),
},
{ key: '$id', header: 'ID' }, { key: '$id', header: 'ID' },
{ key: '$createdAt', header: 'Data de Criação', render: row => {
new Date(row.$createdAt).toLocaleDateString('pt-BR') }, key: '$createdAt',
header: 'Data de Criação',
render: row => new Date(row.$createdAt).toLocaleDateString('pt-BR'),
},
]; ];
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
@ -99,10 +104,10 @@ const PagamentoList: React.FC<PagamentoListProps> = ({
<DataTable <DataTable
columns={columns} columns={columns}
data={pagamentos} data={pagamentos}
actions={(pag) => ( actions={pagamento => (
<TableActions <TableActions
onEdit={() => onEdit(pag)} onEdit={() => onEdit(pagamento)}
onDelete={() => handleDelete(pag.$id)} onDelete={() => handleDelete(pagamento.$id)}
/> />
)} )}
/> />
@ -130,5 +135,3 @@ const PagamentoList: React.FC<PagamentoListProps> = ({
}; };
export default PagamentoList; export default PagamentoList;

View file

@ -1,6 +1,6 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from 'react';
import { Models } from "@/lib/appwrite"; import { catalogoProdutoService } from '../services/catalogoProdutoService';
import { catalogoProdutoService } from "../services/catalogoProdutoService"; import { CatalogoProdutoDocument } from '@/types/legacyEntities';
export interface CatalogoProdutoFormData { export interface CatalogoProdutoFormData {
descricao: string; descricao: string;
@ -24,7 +24,7 @@ export interface CatalogoProdutoStats {
} }
export interface UseCatalogoProdutosReturn { export interface UseCatalogoProdutosReturn {
catalogoProdutos: Models.Document[]; catalogoProdutos: CatalogoProdutoDocument[];
loading: boolean; loading: boolean;
isChangingPage: boolean; isChangingPage: boolean;
isCreating: boolean; isCreating: boolean;
@ -44,20 +44,20 @@ const PAGE_SIZE = 10;
const toApiPayload = (data: CatalogoProdutoFormData) => ({ const toApiPayload = (data: CatalogoProdutoFormData) => ({
descricao: data.descricao, descricao: data.descricao,
"codigo-interno": data.codigo_interno, 'codigo-interno': data.codigo_interno,
"codigo-ean": data.codigo_ean, 'codigo-ean': data.codigo_ean,
"preco-fabrica": data.preco_fabrica, 'preco-fabrica': data.preco_fabrica,
pmc: data.pmc, pmc: data.pmc,
"desconto-comercial": data.desconto_comercial, 'desconto-comercial': data.desconto_comercial,
"valor-substituicao-tributaria": data.valor_substituicao_tributaria, 'valor-substituicao-tributaria': data.valor_substituicao_tributaria,
"preco-nf": data.preco_nf, 'preco-nf': data.preco_nf,
laboratorio: data.laboratorio, laboratorio: data.laboratorio,
categoria: data.categoria, categoria: data.categoria,
subcategoria: data.subcategoria, subcategoria: data.subcategoria,
}); });
export const useCatalogoProdutos = (): UseCatalogoProdutosReturn => { export const useCatalogoProdutos = (): UseCatalogoProdutosReturn => {
const [catalogoProdutos, setCatalogoProdutos] = useState<Models.Document[]>([]); const [catalogoProdutos, setCatalogoProdutos] = useState<CatalogoProdutoDocument[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [totalCatalogoProdutos, setTotalCatalogoProdutos] = useState(0); const [totalCatalogoProdutos, setTotalCatalogoProdutos] = useState(0);
@ -65,18 +65,18 @@ export const useCatalogoProdutos = (): UseCatalogoProdutosReturn => {
const [isChangingPage, setIsChangingPage] = useState(false); const [isChangingPage, setIsChangingPage] = useState(false);
const [isCreating, setIsCreating] = useState(false); const [isCreating, setIsCreating] = useState(false);
const listarCatalogo = useCallback(async (page = 1, termoBusca = "") => { const listarCatalogo = useCallback(async (page = 1, termoBusca = '') => {
setLoading(true); setLoading(true);
setIsChangingPage(true); setIsChangingPage(true);
setError(null); setError(null);
try { try {
const response = await catalogoProdutoService.listar(page, PAGE_SIZE, termoBusca); const response = await catalogoProdutoService.listar(page, PAGE_SIZE, termoBusca);
setCatalogoProdutos(response.documents as Models.Document[]); setCatalogoProdutos(response.documents);
setTotalCatalogoProdutos(response.total); setTotalCatalogoProdutos(response.total);
setCurrentPage(page); setCurrentPage(page);
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : "Erro ao listar catálogo de produtos"; const message = err instanceof Error ? err.message : 'Erro ao listar catálogo de produtos';
setError(message); setError(message);
setCatalogoProdutos([]); setCatalogoProdutos([]);
setTotalCatalogoProdutos(0); setTotalCatalogoProdutos(0);
@ -86,99 +86,83 @@ export const useCatalogoProdutos = (): UseCatalogoProdutosReturn => {
} }
}, []); }, []);
const buscarCatalogo = useCallback( const buscarCatalogo = useCallback(async (params: { termo: string; filtros: unknown }, page = 1) => {
async (params: { termo: string; filtros: unknown }, page = 1) => { await listarCatalogo(page, params.termo);
await listarCatalogo(page, params.termo); }, [listarCatalogo]);
},
[listarCatalogo]
);
const cadastrarCatalogo = useCallback( const cadastrarCatalogo = useCallback(async (data: CatalogoProdutoFormData): Promise<boolean> => {
async (data: CatalogoProdutoFormData): Promise<boolean> => { setIsCreating(true);
setIsCreating(true); setError(null);
setError(null);
try { try {
await catalogoProdutoService.criar(toApiPayload(data)); await catalogoProdutoService.criar(toApiPayload(data));
await listarCatalogo(currentPage); await listarCatalogo(currentPage);
return true; return true;
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : "Erro ao cadastrar produto"; const message = err instanceof Error ? err.message : 'Erro ao cadastrar produto';
setError(message); setError(message);
return false; return false;
} finally { } finally {
setIsCreating(false); setIsCreating(false);
} }
}, }, [currentPage, listarCatalogo]);
[currentPage, listarCatalogo]
);
const atualizarCatalogo = useCallback( const atualizarCatalogo = useCallback(async (id: string, data: CatalogoProdutoFormData): Promise<boolean> => {
async (id: string, data: CatalogoProdutoFormData): Promise<boolean> => { setLoading(true);
setLoading(true); setError(null);
setError(null);
try { try {
await catalogoProdutoService.atualizar(id, toApiPayload(data)); await catalogoProdutoService.atualizar(id, toApiPayload(data));
await listarCatalogo(currentPage); await listarCatalogo(currentPage);
return true; return true;
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : "Erro ao atualizar produto"; const message = err instanceof Error ? err.message : 'Erro ao atualizar produto';
setError(message); setError(message);
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, }, [currentPage, listarCatalogo]);
[currentPage, listarCatalogo]
);
const deletarCatalogo = useCallback( const deletarCatalogo = useCallback(async (id: string): Promise<boolean> => {
async (id: string): Promise<boolean> => { setLoading(true);
setLoading(true); setError(null);
setError(null);
try { try {
await catalogoProdutoService.deletar(id); await catalogoProdutoService.deletar(id);
await listarCatalogo(currentPage); await listarCatalogo(currentPage);
return true; return true;
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : "Erro ao deletar produto"; const message = err instanceof Error ? err.message : 'Erro ao deletar produto';
setError(message); setError(message);
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, }, [currentPage, listarCatalogo]);
[currentPage, listarCatalogo]
);
const obterEstatisticas = useCallback((): CatalogoProdutoStats => { const obterEstatisticas = useCallback((): CatalogoProdutoStats => {
return catalogoProdutos.reduce<CatalogoProdutoStats>( return catalogoProdutos.reduce<CatalogoProdutoStats>((stats, produto) => {
(stats, produto) => { const categoria = typeof produto.categoria === 'string' ? produto.categoria : 'Sem categoria';
const produtoData = produto as Models.Document & Record<string, unknown>;
const categoria = typeof produtoData.categoria === "string" ? produtoData.categoria : "Sem categoria";
stats.total += 1; stats.total += 1;
if (produtoData.laboratorio) { if (produto.laboratorio) {
stats.comLaboratorio += 1; stats.comLaboratorio += 1;
}
if (typeof produtoData["preco-nf"] === "number" && produtoData["preco-nf"] > 0) {
stats.comPreco += 1;
}
stats.porCategoria[categoria] = (stats.porCategoria[categoria] || 0) + 1;
return stats;
},
{
total: 0,
comLaboratorio: 0,
comPreco: 0,
porCategoria: {},
} }
);
if (typeof produto['preco-nf'] === 'number' && produto['preco-nf'] > 0) {
stats.comPreco += 1;
}
stats.porCategoria[categoria] = (stats.porCategoria[categoria] || 0) + 1;
return stats;
}, {
total: 0,
comLaboratorio: 0,
comPreco: 0,
porCategoria: {},
});
}, [catalogoProdutos]); }, [catalogoProdutos]);
return { return {
@ -198,4 +182,3 @@ export const useCatalogoProdutos = (): UseCatalogoProdutosReturn => {
setCurrentPage, setCurrentPage,
}; };
}; };

View file

@ -1,11 +1,10 @@
// @ts-nocheck
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Models } from '@/lib/appwrite';
import { pagamentoService, PagamentoData } from '@/services/pagamentoService'; import { pagamentoService, PagamentoData } from '@/services/pagamentoService';
import { PagamentoDocument } from '@/types/legacyEntities';
export type { PagamentoData }; export type { PagamentoData };
export interface UsePagamentosReturn { export interface UsePagamentosReturn {
pagamentos: Models.Document[]; pagamentos: PagamentoDocument[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
totalPagamentos: number; totalPagamentos: number;
@ -21,53 +20,43 @@ export interface UsePagamentosReturn {
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
export const usePagamentos = (): UsePagamentosReturn => { export const usePagamentos = (): UsePagamentosReturn => {
const [pagamentos, setPagamentos] = useState<Models.Document[]>([]); const [pagamentos, setPagamentos] = useState<PagamentoDocument[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [totalPagamentos, setTotalPagamentos] = useState(0); const [totalPagamentos, setTotalPagamentos] = useState(0);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const listarPagamentos = useCallback( const listarPagamentos = useCallback(async (page = currentPage, search = '') => {
async (page = currentPage, search = '') => {
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
const response = await pagamentoService.listar(page, PAGE_SIZE, search); const response = await pagamentoService.listar(page, PAGE_SIZE, search);
setPagamentos(response.documents || []); setPagamentos(response.documents || []);
setTotalPagamentos(response.total || 0); setTotalPagamentos(response.total || 0);
setCurrentPage(page); setCurrentPage(page);
} catch (err) { } catch {
setError('Erro de conexão ao carregar pagamentos'); setError('Erro de conexão ao carregar pagamentos');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [currentPage]); }, [currentPage]);
const buscarPagamentos = useCallback( const buscarPagamentos = useCallback(async (termo: string, page = 1) => {
async (termo: string, page = 1) => { setLoading(true);
setLoading(true); setError(null);
setError(null);
try { try {
const response = await pagamentoService.buscarPorNome( const response = await pagamentoService.buscarPorNome(termo, page, PAGE_SIZE);
termo, setPagamentos(response.documents || []);
page, setTotalPagamentos(response.total || 0);
PAGE_SIZE setCurrentPage(page);
); } catch {
setError('Erro de conexão ao buscar pagamentos');
setPagamentos(response.documents || []); } finally {
setTotalPagamentos(response.total || 0); setLoading(false);
setCurrentPage(page); }
} catch (err) { }, []);
setError('Erro de conexão ao buscar pagamentos');
} finally {
setLoading(false);
}
},
[]
);
const cadastrarPagamento = useCallback(async (formData: PagamentoData): Promise<boolean> => { const cadastrarPagamento = useCallback(async (formData: PagamentoData): Promise<boolean> => {
setLoading(true); setLoading(true);
@ -77,8 +66,8 @@ export const usePagamentos = (): UsePagamentosReturn => {
await pagamentoService.criar(formData); await pagamentoService.criar(formData);
await listarPagamentos(currentPage); await listarPagamentos(currentPage);
return true; return true;
} catch (err) { } catch {
setError('Erro de conexão ao cadastrar pagamento'); setError('Erro de conexão ao cadastrar pagamento');
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
@ -93,8 +82,8 @@ export const usePagamentos = (): UsePagamentosReturn => {
await pagamentoService.atualizar(id, formData); await pagamentoService.atualizar(id, formData);
await listarPagamentos(currentPage); await listarPagamentos(currentPage);
return true; return true;
} catch (err) { } catch {
setError('Erro de conexão ao atualizar pagamento'); setError('Erro de conexão ao atualizar pagamento');
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
@ -109,8 +98,8 @@ export const usePagamentos = (): UsePagamentosReturn => {
await pagamentoService.deletar(id); await pagamentoService.deletar(id);
await listarPagamentos(currentPage); await listarPagamentos(currentPage);
return true; return true;
} catch (err) { } catch {
setError('Erro de conexão ao deletar pagamento'); setError('Erro de conexão ao deletar pagamento');
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
@ -131,4 +120,3 @@ export const usePagamentos = (): UsePagamentosReturn => {
setCurrentPage, setCurrentPage,
}; };
}; };

View file

@ -1,31 +1,33 @@
export interface PagamentoData { import { PagamentoDocument, ServiceResponse } from '@/types/legacyEntities';
export interface PagamentoData {
id?: string; id?: string;
pedidos?: string[]; pedidos?: string[];
status?: string; status?: string;
metodo?: string; metodo?: string;
valor?: number; valor?: number;
[key: string]: any;
} }
interface ServiceResponse<T = unknown> { const emptyList = (): ServiceResponse<PagamentoDocument> => ({
success: boolean; success: true,
data?: T; data: null,
documents?: T[]; documents: [],
total?: number; total: 0,
error?: string; });
message?: string;
}
const emptyList = <T,>(): ServiceResponse<T> => ({ success: true, documents: [], total: 0 }); const removed = (message = 'Service removido'): ServiceResponse<PagamentoDocument> => ({
const removed = (message = 'Service removido'): ServiceResponse => ({ success: false, error: message, message }); success: false,
error: message,
message,
});
export const pagamentoService = { export const pagamentoService = {
listar: async (_page = 1, _limit = 10, _search = '') => emptyList<PagamentoData>(), listar: async (_page = 1, _limit = 10, _search = '') => emptyList(),
buscarPorNome: async (_nome: string, _page = 1, _limit = 10) => emptyList<PagamentoData>(), buscarPorNome: async (_nome: string, _page = 1, _limit = 10) => emptyList(),
criar: async (_data?: PagamentoData) => removed(), criar: async (_data?: PagamentoData) => removed(),
criarPagamento: async (_data?: PagamentoData) => removed(), criarPagamento: async (_data?: PagamentoData) => removed(),
buscarPorUserId: async () => emptyList<PagamentoData>(), buscarPorUserId: async () => emptyList(),
buscarPorId: async (_id?: string) => ({ success: true, data: null as PagamentoData | null }), buscarPorId: async (_id?: string): Promise<ServiceResponse<PagamentoDocument>> => ({ success: true, data: null }),
atualizar: async (_id?: string, _data?: Partial<PagamentoData>) => removed(), atualizar: async (_id?: string, _data?: Partial<PagamentoData>) => removed(),
deletar: async (_id?: string) => removed(), deletar: async (_id?: string) => removed(),
}; };

View file

@ -13,6 +13,29 @@ export interface FaturaDocument extends LegacyDocument {
nome?: string; nome?: string;
} }
export interface PagamentoDocument extends LegacyDocument {
pedidos?: string[] | { $id?: string } | string | null;
status?: string;
metodo?: string;
valor?: number | string;
}
export interface CatalogoProdutoDocument extends LegacyDocument {
descricao: string;
nome?: string;
'codigo-interno'?: string;
'codigo-ean'?: string;
'preco-original'?: number;
'preco-atual'?: number;
'preco-fabrica'?: number;
'preco-nf'?: number;
pmc?: number;
quantidade?: number;
laboratorio?: string;
categoria?: string;
subcategoria?: string;
}
export interface ServiceResponse<TDocument> { export interface ServiceResponse<TDocument> {
success: boolean; success: boolean;
data?: TDocument | null; data?: TDocument | null;