refactor(frontend): replace legacy document typing for billing and categories

This commit is contained in:
Tiago Yamamoto 2026-03-07 07:49:58 -06:00
parent 6cde64f424
commit b633939d4e
10 changed files with 326 additions and 310 deletions

View file

@ -1,4 +1,4 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@ -11,14 +11,15 @@ import FaturaList from '@/components/FaturaList';
import { useFaturas, FaturaFormData } from '@/hooks/useFaturas'; import { useFaturas, FaturaFormData } from '@/hooks/useFaturas';
import { RoleGuard } from '@/components/auth/RoleGuard'; import { RoleGuard } from '@/components/auth/RoleGuard';
import { UserRole } from '@/types/auth'; import { UserRole } from '@/types/auth';
import { FaturaDocument } from '@/types/legacyEntities';
const GestaoFaturas = () => { const GestaoFaturas = () => {
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<FaturaDocument | null>(null);
const [activeTab, setActiveTab] = useState<'lista' | 'cadastro'>('lista'); const [activeTab, setActiveTab] = useState<'lista' | 'cadastro'>('lista');
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const { const {
faturas, faturas,
loading, loading,
@ -30,21 +31,19 @@ const GestaoFaturas = () => {
cadastrarFatura, cadastrarFatura,
atualizarFatura, atualizarFatura,
deletarFatura, deletarFatura,
setCurrentPage
} = useFaturas(); } = useFaturas();
// 🔐 Verificar autenticação
useEffect(() => { useEffect(() => {
const initializeUser = async () => { const initializeUser = async () => {
try { try {
const currentUser = await account.get(); const currentUser = await account.get();
setUser(currentUser); setUser(currentUser);
if (activeTab === 'lista') { if (activeTab === 'lista') {
await listarFaturas(); await listarFaturas();
} }
} catch (error) { } catch (error) {
console.error('❌ Usuário não autenticado:', error); console.error('Usuário não autenticado:', error);
router.push('/'); router.push('/');
} }
}; };
@ -60,16 +59,16 @@ const GestaoFaturas = () => {
setActiveTab('lista'); setActiveTab('lista');
} }
return success; return success;
} else {
const success = await cadastrarFatura(formData);
if (success) {
setTimeout(() => setActiveTab('lista'), 2000);
}
return success;
} }
const success = await cadastrarFatura(formData);
if (success) {
setTimeout(() => setActiveTab('lista'), 2000);
}
return success;
}; };
const handleEdit = (fatura: Models.Document) => { const handleEdit = (fatura: FaturaDocument) => {
setEditing(fatura); setEditing(fatura);
setActiveTab('cadastro'); setActiveTab('cadastro');
}; };
@ -80,33 +79,40 @@ const GestaoFaturas = () => {
}; };
const handlePrevPage = async () => { const handlePrevPage = async () => {
if (currentPage > 1) { if (currentPage <= 1) {
if (searchTerm.trim()) { return;
await buscarFaturas(searchTerm, currentPage - 1);
} else {
await listarFaturas(currentPage - 1);
}
} }
if (searchTerm.trim()) {
await buscarFaturas(searchTerm, currentPage - 1);
return;
}
await listarFaturas(currentPage - 1);
}; };
const handleSearch = async (term: string) => { const handleSearch = async (term: string) => {
setSearchTerm(term); setSearchTerm(term);
if (term.trim()) { if (term.trim()) {
await buscarFaturas(term, 1); await buscarFaturas(term, 1);
} else { return;
await listarFaturas(1);
} }
await listarFaturas(1);
}; };
const handleNextPage = async () => { const handleNextPage = async () => {
const totalPages = Math.ceil(totalFaturas / 10); const totalPages = Math.ceil(totalFaturas / 10);
if (currentPage < totalPages) { if (currentPage >= totalPages) {
if (searchTerm.trim()) { return;
await buscarFaturas(searchTerm, currentPage + 1);
} else {
await listarFaturas(currentPage + 1);
}
} }
if (searchTerm.trim()) {
await buscarFaturas(searchTerm, currentPage + 1);
return;
}
await listarFaturas(currentPage + 1);
}; };
if (!user) { if (!user) {
@ -114,7 +120,7 @@ const GestaoFaturas = () => {
<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>
); );
@ -123,75 +129,73 @@ const GestaoFaturas = () => {
return ( return (
<RoleGuard allowedRoles={[UserRole.ADMIN, UserRole.ADMIN]}> <RoleGuard allowedRoles={[UserRole.ADMIN, UserRole.ADMIN]}>
<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 Faturas" title="Gestão de Faturas"
subtitle="Gerencie as faturas da plataforma SaveInMed" subtitle="Gerencie as faturas da plataforma SaveInMed"
/> />
<main className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{/* Navegação por Tabs */}
<div className="mb-6">
<nav className="flex space-x-8 bg-white p-4 rounded-lg shadow-sm">
<button
onClick={() => {
setActiveTab('lista');
setEditing(null);
}}
className={`py-2 px-4 border-b-2 font-medium text-sm transition-colors cursor-pointer rounded-t-md ${
activeTab === 'lista'
? 'border-blue-500 text-blue-600 bg-blue-50'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 hover:bg-gray-50'
}`}
>
<FileSpreadsheet className="w-4 h-4 inline-block mr-2" /> Listar Faturas
</button>
<button
onClick={() => {
setActiveTab('cadastro');
setEditing(null);
}}
className={`py-2 px-4 border-b-2 font-medium text-sm transition-colors cursor-pointer rounded-t-md ${
activeTab === 'cadastro'
? 'border-blue-500 text-blue-600 bg-blue-50'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 hover:bg-gray-50'
}`}
>
<Plus className="w-4 h-4 inline-block mr-2" /> Cadastrar Fatura
</button>
</nav>
</div>
{/* Conteúdo das Tabs */} <main className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{activeTab === 'lista' && ( <div className="mb-6">
<FaturaList <nav className="flex space-x-8 bg-white p-4 rounded-lg shadow-sm">
faturas={faturas} <button
loading={loading} onClick={() => {
error={error} setActiveTab('lista');
totalFaturas={totalFaturas} setEditing(null);
currentPage={currentPage} }}
pageSize={10} className={`py-2 px-4 border-b-2 font-medium text-sm transition-colors cursor-pointer rounded-t-md ${
onEdit={handleEdit} activeTab === 'lista'
onDelete={deletarFatura} ? 'border-blue-500 text-blue-600 bg-blue-50'
onRefresh={() => : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 hover:bg-gray-50'
searchTerm.trim() }`}
? buscarFaturas(searchTerm, currentPage) >
: listarFaturas(currentPage) <FileSpreadsheet className="w-4 h-4 inline-block mr-2" /> Listar Faturas
} </button>
onPrevPage={handlePrevPage} <button
onNextPage={handleNextPage} onClick={() => {
onSearch={handleSearch} setActiveTab('cadastro');
/> setEditing(null);
)} }}
className={`py-2 px-4 border-b-2 font-medium text-sm transition-colors cursor-pointer rounded-t-md ${
activeTab === 'cadastro'
? 'border-blue-500 text-blue-600 bg-blue-50'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 hover:bg-gray-50'
}`}
>
<Plus className="w-4 h-4 inline-block mr-2" /> Cadastrar Fatura
</button>
</nav>
</div>
{activeTab === 'cadastro' && ( {activeTab === 'lista' && (
<FaturaForm <FaturaList
onSubmit={handleFormSubmit} faturas={faturas}
onCancel={editing ? handleCancelEdit : undefined} loading={loading}
initialData={editing} error={error}
loading={loading} totalFaturas={totalFaturas}
/> currentPage={currentPage}
)} pageSize={10}
onEdit={handleEdit}
onDelete={deletarFatura}
onRefresh={() =>
searchTerm.trim()
? buscarFaturas(searchTerm, currentPage)
: listarFaturas(currentPage)
}
onPrevPage={handlePrevPage}
onNextPage={handleNextPage}
onSearch={handleSearch}
/>
)}
{activeTab === 'cadastro' && (
<FaturaForm
onSubmit={handleFormSubmit}
onCancel={editing ? handleCancelEdit : undefined}
initialData={editing}
loading={loading}
/>
)}
</main> </main>
</div> </div>
</RoleGuard> </RoleGuard>
@ -199,4 +203,3 @@ const GestaoFaturas = () => {
}; };
export default GestaoFaturas; export default GestaoFaturas;

View file

@ -1,67 +1,66 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Models } from '@/lib/appwrite';
import { CategoriaFormData } from '@/hooks/useCategorias'; import { CategoriaFormData } from '@/hooks/useCategorias';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { CategoriaDocument } from '@/types/legacyEntities';
interface CategoriaFormProps { interface CategoriaFormProps {
onSubmit: (data: CategoriaFormData) => Promise<boolean>; onSubmit: (data: CategoriaFormData) => Promise<boolean>;
onCancel?: () => void; onCancel?: () => void;
initialData?: Models.Document | null; initialData?: CategoriaDocument | null;
loading?: boolean; loading?: boolean;
} }
type CategoriaDocument = Models.Document & {
nome?: string;
};
const CategoriaForm: React.FC<CategoriaFormProps> = ({ const CategoriaForm: React.FC<CategoriaFormProps> = ({
onSubmit, onSubmit,
onCancel, onCancel,
initialData, initialData,
loading = false loading = false,
}) => { }) => {
const [formData, setFormData] = useState<CategoriaFormData>({ const [formData, setFormData] = useState<CategoriaFormData>({
nome: '' nome: '',
}); });
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 document = initialData as CategoriaDocument; setFormData({ nome: initialData.nome || '' });
setFormData({ nome: document.nome || '' }); return;
} else {
setFormData({ nome: '' });
} }
setFormData({ nome: '' });
}, [initialData]); }, [initialData]);
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const success = await onSubmit(formData); const success = await onSubmit(formData);
if (success) { if (success) {
setMessage({ setMessage({
type: 'success', type: 'success',
text: initialData ? '🔄 Categoria atualizada com sucesso!' : '🎉 Categoria cadastrada com sucesso!' text: initialData ? 'Categoria atualizada com sucesso!' : 'Categoria cadastrada com sucesso!',
}); });
if (!initialData) { if (!initialData) {
setFormData({ nome: '' }); setFormData({ nome: '' });
} }
setTimeout(() => setMessage(null), 3000); setTimeout(() => setMessage(null), 3000);
} else { return;
setMessage({
type: 'error',
text: initialData ? 'Erro ao atualizar categoria' : 'Erro ao cadastrar categoria'
});
} }
setMessage({
type: 'error',
text: initialData ? 'Erro ao atualizar categoria' : 'Erro ao cadastrar categoria',
});
}; };
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target; const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value })); setFormData(prev => ({ ...prev, [name]: value }));
if (message) setMessage(null); if (message) {
setMessage(null);
}
}; };
return ( return (
@ -109,7 +108,7 @@ const CategoriaForm: React.FC<CategoriaFormProps> = ({
> >
{loading ? 'Processando...' : (initialData ? 'Atualizar' : <><Plus className="w-4 h-4 inline-block mr-1" /> Cadastrar</>)} {loading ? 'Processando...' : (initialData ? 'Atualizar' : <><Plus className="w-4 h-4 inline-block mr-1" /> Cadastrar</>)}
</button> </button>
{onCancel && ( {onCancel && (
<button <button
type="button" type="button"
@ -127,4 +126,3 @@ const CategoriaForm: React.FC<CategoriaFormProps> = ({
}; };
export default CategoriaForm; export default CategoriaForm;

View file

@ -1,19 +1,19 @@
import React from 'react'; import React from 'react';
import { Models } from '@/lib/appwrite';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
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 { CategoriaDocument } from '@/types/legacyEntities';
interface CategoriaListProps { interface CategoriaListProps {
categorias: Models.Document[]; categorias: CategoriaDocument[];
loading: boolean; loading: boolean;
isChangingPage?: boolean; isChangingPage?: boolean;
error: string | null; error: string | null;
totalCategorias: number; totalCategorias: number;
currentPage: number; currentPage: number;
pageSize: number; pageSize: number;
onEdit: (lab: Models.Document) => void; onEdit: (categoria: CategoriaDocument) => void;
onDelete: (id: string) => Promise<boolean>; onDelete: (id: string) => Promise<boolean>;
onPrevPage: () => void; onPrevPage: () => void;
onNextPage: () => void; onNextPage: () => void;
@ -32,7 +32,7 @@ const CategoriaList: React.FC<CategoriaListProps> = ({
onDelete, onDelete,
onPrevPage, onPrevPage,
onNextPage, onNextPage,
onSearch onSearch,
}) => { }) => {
const totalPages = Math.ceil(totalCategorias / pageSize); const totalPages = Math.ceil(totalCategorias / pageSize);
const startItem = (currentPage - 1) * pageSize + 1; const startItem = (currentPage - 1) * pageSize + 1;
@ -50,10 +50,14 @@ const CategoriaList: React.FC<CategoriaListProps> = ({
} }
}; };
const columns: Column<Models.Document>[] = [ const columns: Column<CategoriaDocument>[] = [
{ key: 'nome', header: 'Nome' }, { key: 'nome', header: 'Nome' },
{ 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'),
},
]; ];
return ( return (
@ -77,7 +81,7 @@ const CategoriaList: React.FC<CategoriaListProps> = ({
<div className="bg-blue-50 border border-blue-200 rounded-md p-3 mb-4"> <div className="bg-blue-50 border border-blue-200 rounded-md p-3 mb-4">
<div className="flex items-center"> <div className="flex items-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div> <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
<span className="text-blue-800 text-sm">Carregando página...</span> <span className="text-blue-800 text-sm">Carregando página...</span>
</div> </div>
</div> </div>
)} )}
@ -91,10 +95,10 @@ const CategoriaList: React.FC<CategoriaListProps> = ({
<DataTable <DataTable
columns={columns} columns={columns}
data={categorias} data={categorias}
actions={(lab) => ( actions={categoria => (
<TableActions <TableActions
onEdit={() => onEdit(lab)} onEdit={() => onEdit(categoria)}
onDelete={() => handleDelete(lab.$id)} onDelete={() => handleDelete(categoria.$id)}
/> />
)} )}
/> />
@ -122,4 +126,4 @@ const CategoriaList: React.FC<CategoriaListProps> = ({
); );
}; };
export default CategoriaList; export default CategoriaList;

View file

@ -1,19 +1,15 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Models } from '@/lib/appwrite';
import { FaturaFormData } from '@/hooks/useFaturas'; import { FaturaFormData } from '@/hooks/useFaturas';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { FaturaDocument } from '@/types/legacyEntities';
interface FaturaFormProps { interface FaturaFormProps {
onSubmit: (data: FaturaFormData) => Promise<boolean>; onSubmit: (data: FaturaFormData) => Promise<boolean>;
onCancel?: () => void; onCancel?: () => void;
initialData?: Models.Document | null; initialData?: FaturaDocument | null;
loading?: boolean; loading?: boolean;
} }
type FaturaDocument = Models.Document & {
nome?: string;
};
const FaturaForm: React.FC<FaturaFormProps> = ({ const FaturaForm: React.FC<FaturaFormProps> = ({
onSubmit, onSubmit,
onCancel, onCancel,
@ -25,41 +21,45 @@ const FaturaForm: React.FC<FaturaFormProps> = ({
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
const document = initialData as FaturaDocument; setFormData({ nome: initialData.nome || '' });
setFormData({ nome: document.nome || '' }); return;
} else {
setFormData({ nome: '' });
} }
setFormData({ nome: '' });
}, [initialData]); }, [initialData]);
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const success = await onSubmit(formData); const success = await onSubmit(formData);
if (success) { if (success) {
setMessage({ setMessage({
type: 'success', type: 'success',
text: initialData ? '🔄 Fatura atualizada com sucesso!' : '🎉 Fatura cadastrada com sucesso!' text: initialData ? 'Fatura atualizada com sucesso!' : 'Fatura cadastrada com sucesso!',
}); });
if (!initialData) { if (!initialData) {
setFormData({ nome: '' }); setFormData({ nome: '' });
} }
setTimeout(() => setMessage(null), 3000); setTimeout(() => setMessage(null), 3000);
} else { return;
setMessage({ type: 'error', text: initialData ? 'Erro ao atualizar fatura' : 'Erro ao cadastrar fatura' });
} }
setMessage({ type: 'error', text: initialData ? 'Erro ao atualizar fatura' : 'Erro ao cadastrar fatura' });
}; };
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target; const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value })); setFormData(prev => ({ ...prev, [name]: value }));
if (message) setMessage(null); if (message) {
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 Fatura' : <><Plus className="w-5 h-5 inline-block mr-2" /> Nova Fatura</>} {initialData ? 'Editar Fatura' : <><Plus className="w-5 h-5 inline-block mr-2" /> Nova Fatura</>}
</h2> </h2>
<p className="text-gray-600"> <p className="text-gray-600">
{initialData ? 'Atualize os dados da fatura.' : 'Cadastre uma nova fatura na plataforma SaveInMed.'} {initialData ? 'Atualize os dados da fatura.' : 'Cadastre uma nova fatura na plataforma SaveInMed.'}
@ -95,7 +95,7 @@ const FaturaForm: React.FC<FaturaFormProps> = ({
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 && (
<button <button
@ -104,7 +104,7 @@ const FaturaForm: React.FC<FaturaFormProps> = ({
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>
@ -114,5 +114,3 @@ const FaturaForm: React.FC<FaturaFormProps> = ({
}; };
export default FaturaForm; export default FaturaForm;

View file

@ -1,20 +1,20 @@
import React from 'react'; import React from 'react';
import { Models } from '@/lib/appwrite';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
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 { FaturaDocument } from '@/types/legacyEntities';
interface FaturaListProps { interface FaturaListProps {
faturas: Models.Document[]; faturas: FaturaDocument[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
totalFaturas: number; totalFaturas: number;
currentPage: number; currentPage: number;
pageSize: number; pageSize: number;
onEdit: (fatura: Models.Document) => void; onEdit: (fatura: FaturaDocument) => void;
onDelete: (id: string) => Promise<boolean>; onDelete: (id: string) => Promise<boolean>;
onRefresh: () => void; onRefresh: () => void;
onPrevPage: () => void; onPrevPage: () => void;
@ -34,7 +34,7 @@ const FaturaList: React.FC<FaturaListProps> = ({
onRefresh, onRefresh,
onPrevPage, onPrevPage,
onNextPage, onNextPage,
onSearch onSearch,
}) => { }) => {
const totalPages = Math.ceil(totalFaturas / pageSize); const totalPages = Math.ceil(totalFaturas / pageSize);
const startItem = (currentPage - 1) * pageSize + 1; const startItem = (currentPage - 1) * pageSize + 1;
@ -52,10 +52,14 @@ const FaturaList: React.FC<FaturaListProps> = ({
} }
}; };
const columns: Column<Models.Document>[] = [ const columns: Column<FaturaDocument>[] = [
{ key: 'nome', header: 'Nome' }, { key: 'nome', header: 'Nome' },
{ 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'),
},
]; ];
return ( return (
@ -85,7 +89,7 @@ const FaturaList: React.FC<FaturaListProps> = ({
<DataTable <DataTable
columns={columns} columns={columns}
data={faturas} data={faturas}
actions={(fatura) => ( actions={fatura => (
<TableActions <TableActions
onEdit={() => onEdit(fatura)} onEdit={() => onEdit(fatura)}
onDelete={() => handleDelete(fatura.$id)} onDelete={() => handleDelete(fatura.$id)}

View file

@ -1,13 +1,13 @@
import { useState, useCallback, useRef, useEffect } from 'react'; import { useState, useCallback, useRef, useEffect } from 'react';
import { Models } from '@/lib/appwrite';
import { categoriaService } from '@/services/categoriaService'; import { categoriaService } from '@/services/categoriaService';
import { CategoriaDocument } from '@/types/legacyEntities';
export interface CategoriaFormData { export interface CategoriaFormData {
nome: string; nome: string;
} }
export interface UseCategoriasReturn { export interface UseCategoriasReturn {
categorias: Models.Document[]; categorias: CategoriaDocument[];
loading: boolean; loading: boolean;
isChangingPage: boolean; isChangingPage: boolean;
isCreating: boolean; isCreating: boolean;
@ -27,7 +27,7 @@ export interface UseCategoriasReturn {
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
export const useCategorias = (): UseCategoriasReturn => { export const useCategorias = (): UseCategoriasReturn => {
const [categorias, setCategorias] = useState<Models.Document[]>([]); const [categorias, setCategorias] = useState<CategoriaDocument[]>([]);
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 [totalCategorias, setTotalCategorias] = useState(0); const [totalCategorias, setTotalCategorias] = useState(0);
@ -37,9 +37,7 @@ export const useCategorias = (): UseCategoriasReturn => {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const isInitialMount = useRef(true); const isInitialMount = useRef(true);
// Listagem via API const listarCategorias = useCallback(async (page = currentPage, search = searchTerm) => {
const listarCategorias = useCallback(
async (page = currentPage, search = searchTerm) => {
setIsChangingPage(true); setIsChangingPage(true);
setLoading(true); setLoading(true);
setError(null); setError(null);
@ -50,53 +48,47 @@ export const useCategorias = (): UseCategoriasReturn => {
: await categoriaService.listar(page, PAGE_SIZE); : await categoriaService.listar(page, PAGE_SIZE);
if (response.success) { if (response.success) {
setCategorias((response.documents || []) as Models.Document[]); setCategorias(response.documents || []);
setTotalCategorias(response.total || 0); setTotalCategorias(response.total || 0);
setCurrentPage(page); setCurrentPage(page);
setSearchTerm(search); setSearchTerm(search);
} else { return;
setError(response.error || 'Erro ao carregar categorias');
} }
} catch (err) {
setError('Erro de conexão ao carregar categorias'); setError(response.error || 'Erro ao carregar categorias');
} catch {
setError('Erro de conexão ao carregar categorias');
} finally { } finally {
setLoading(false); setLoading(false);
setIsChangingPage(false); setIsChangingPage(false);
} }
}, [currentPage, searchTerm]); }, [currentPage, searchTerm]);
const buscarCategorias = useCallback( const buscarCategorias = useCallback(async (nome: string, page = 1) => {
async (nome: string, page = 1) => { setIsChangingPage(true);
setIsChangingPage(true); setLoading(true);
setLoading(true); setError(null);
setError(null);
try { try {
const response = await categoriaService.buscarPorNome( const response = await categoriaService.buscarPorNome(nome, page, PAGE_SIZE);
nome,
page,
PAGE_SIZE
);
if (response.success) { if (response.success) {
setCategorias((response.documents || []) as Models.Document[]); setCategorias(response.documents || []);
setTotalCategorias(response.total || 0); setTotalCategorias(response.total || 0);
setCurrentPage(page); setCurrentPage(page);
setSearchTerm(nome); setSearchTerm(nome);
} else { return;
setError(response.error || 'Erro ao buscar categorias');
}
} catch (err) {
setError('Erro de conexão ao buscar categorias');
} finally {
setLoading(false);
setIsChangingPage(false);
} }
},
[]
);
// Operações de criação, atualização e exclusão continuam usando API REST setError(response.error || 'Erro ao buscar categorias');
} catch {
setError('Erro de conexão ao buscar categorias');
} finally {
setLoading(false);
setIsChangingPage(false);
}
}, []);
const cadastrarCategoria = useCallback(async (formData: CategoriaFormData): Promise<boolean> => { const cadastrarCategoria = useCallback(async (formData: CategoriaFormData): Promise<boolean> => {
setIsCreating(true); setIsCreating(true);
setError(null); setError(null);
@ -107,12 +99,12 @@ export const useCategorias = (): UseCategoriasReturn => {
if (response.success) { if (response.success) {
await listarCategorias(1, searchTerm); await listarCategorias(1, searchTerm);
return true; return true;
} else {
setError(response.error || 'Erro ao cadastrar categoria');
return false;
} }
} catch (err) {
setError('Erro de conexão ao cadastrar categoria'); setError(response.error || 'Erro ao cadastrar categoria');
return false;
} catch {
setError('Erro de conexão ao cadastrar categoria');
return false; return false;
} finally { } finally {
setIsCreating(false); setIsCreating(false);
@ -129,17 +121,17 @@ export const useCategorias = (): UseCategoriasReturn => {
if (response.success) { if (response.success) {
await listarCategorias(currentPage, searchTerm); await listarCategorias(currentPage, searchTerm);
return true; return true;
} else {
setError(response.error || 'Erro ao atualizar categoria');
return false;
} }
} catch (err) {
setError('Erro de conexão ao atualizar categoria'); setError(response.error || 'Erro ao atualizar categoria');
return false;
} catch {
setError('Erro de conexão ao atualizar categoria');
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [currentPage, listarCategorias]); }, [currentPage, listarCategorias, searchTerm]);
const deletarCategoria = useCallback(async (id: string): Promise<boolean> => { const deletarCategoria = useCallback(async (id: string): Promise<boolean> => {
setLoading(true); setLoading(true);
@ -151,17 +143,17 @@ export const useCategorias = (): UseCategoriasReturn => {
if (response.success) { if (response.success) {
await listarCategorias(currentPage, searchTerm); await listarCategorias(currentPage, searchTerm);
return true; return true;
} else {
setError(response.error || 'Erro ao deletar categoria');
return false;
} }
} catch (err) {
setError('Erro de conexão ao deletar categoria'); setError(response.error || 'Erro ao deletar categoria');
return false;
} catch {
setError('Erro de conexão ao deletar categoria');
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [currentPage, listarCategorias]); }, [currentPage, listarCategorias, searchTerm]);
useEffect(() => { useEffect(() => {
if (isInitialMount.current) { if (isInitialMount.current) {
@ -171,9 +163,10 @@ export const useCategorias = (): UseCategoriasReturn => {
if (searchTerm.trim()) { if (searchTerm.trim()) {
buscarCategorias(searchTerm, currentPage); buscarCategorias(searchTerm, currentPage);
} else { return;
listarCategorias(currentPage);
} }
listarCategorias(currentPage);
}, [currentPage, searchTerm, listarCategorias, buscarCategorias]); }, [currentPage, searchTerm, listarCategorias, buscarCategorias]);
return { return {
@ -191,7 +184,6 @@ export const useCategorias = (): UseCategoriasReturn => {
deletarCategoria, deletarCategoria,
setCurrentPage, setCurrentPage,
searchTerm, searchTerm,
setSearchTerm setSearchTerm,
}; };
}; };

View file

@ -1,13 +1,13 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Models } from '@/lib/appwrite';
import { faturaService } from '@/services/faturaService'; import { faturaService } from '@/services/faturaService';
import { FaturaDocument } from '@/types/legacyEntities';
export interface FaturaFormData { export interface FaturaFormData {
nome: string; nome: string;
} }
export interface UseFaturasReturn { export interface UseFaturasReturn {
faturas: Models.Document[]; faturas: FaturaDocument[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
totalFaturas: number; totalFaturas: number;
@ -23,13 +23,12 @@ export interface UseFaturasReturn {
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
export const useFaturas = (): UseFaturasReturn => { export const useFaturas = (): UseFaturasReturn => {
const [faturas, setFaturas] = useState<Models.Document[]>([]); const [faturas, setFaturas] = useState<FaturaDocument[]>([]);
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 [totalFaturas, setTotalFaturas] = useState(0); const [totalFaturas, setTotalFaturas] = useState(0);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
// Listagem usando SDK do Appwrite
const listarFaturas = useCallback(async (page = currentPage) => { const listarFaturas = useCallback(async (page = currentPage) => {
setLoading(true); setLoading(true);
setError(null); setError(null);
@ -38,48 +37,42 @@ export const useFaturas = (): UseFaturasReturn => {
const response = await faturaService.listar(page, PAGE_SIZE); const response = await faturaService.listar(page, PAGE_SIZE);
if (response.success) { if (response.success) {
setFaturas((response.documents || []) as Models.Document[]); setFaturas(response.documents || []);
setTotalFaturas(response.total || 0); setTotalFaturas(response.total || 0);
setCurrentPage(page); setCurrentPage(page);
} else { return;
setError(response.error || 'Erro ao carregar faturas');
} }
} catch (err) {
setError('Erro de conexão ao carregar faturas'); setError(response.error || 'Erro ao carregar faturas');
} catch {
setError('Erro de conexão ao carregar faturas');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [currentPage]); }, [currentPage]);
const buscarFaturas = useCallback( const buscarFaturas = useCallback(async (nome: string, page = 1) => {
async (nome: string, page = 1) => { setLoading(true);
setLoading(true); setError(null);
setError(null);
try { try {
const response = await faturaService.buscarPorNome( const response = await faturaService.buscarPorNome(nome, page, PAGE_SIZE);
nome,
page,
PAGE_SIZE
);
if (response.success) { if (response.success) {
setFaturas((response.documents || []) as Models.Document[]); setFaturas(response.documents || []);
setTotalFaturas(response.total || 0); setTotalFaturas(response.total || 0);
setCurrentPage(page); setCurrentPage(page);
} else { return;
setError(response.error || 'Erro ao buscar faturas');
}
} catch (err) {
setError('Erro de conexão ao buscar faturas');
} finally {
setLoading(false);
} }
},
[]
);
// Operações de criação, atualização e exclusão continuam usando API REST setError(response.error || 'Erro ao buscar faturas');
} catch {
setError('Erro de conexão ao buscar faturas');
} finally {
setLoading(false);
}
}, []);
const cadastrarFatura = useCallback(async (formData: FaturaFormData): Promise<boolean> => { const cadastrarFatura = useCallback(async (formData: FaturaFormData): Promise<boolean> => {
setLoading(true); setLoading(true);
setError(null); setError(null);
@ -90,12 +83,12 @@ export const useFaturas = (): UseFaturasReturn => {
if (response.success) { if (response.success) {
await listarFaturas(currentPage); await listarFaturas(currentPage);
return true; return true;
} else {
setError(response.error || 'Erro ao cadastrar fatura');
return false;
} }
} catch (err) {
setError('Erro de conexão ao cadastrar fatura'); setError(response.error || 'Erro ao cadastrar fatura');
return false;
} catch {
setError('Erro de conexão ao cadastrar fatura');
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
@ -112,12 +105,12 @@ export const useFaturas = (): UseFaturasReturn => {
if (response.success) { if (response.success) {
await listarFaturas(currentPage); await listarFaturas(currentPage);
return true; return true;
} else {
setError(response.error || 'Erro ao atualizar fatura');
return false;
} }
} catch (err) {
setError('Erro de conexão ao atualizar fatura'); setError(response.error || 'Erro ao atualizar fatura');
return false;
} catch {
setError('Erro de conexão ao atualizar fatura');
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
@ -134,12 +127,12 @@ export const useFaturas = (): UseFaturasReturn => {
if (response.success) { if (response.success) {
await listarFaturas(currentPage); await listarFaturas(currentPage);
return true; return true;
} else {
setError(response.error || 'Erro ao deletar fatura');
return false;
} }
} catch (err) {
setError('Erro de conexão ao deletar fatura'); setError(response.error || 'Erro ao deletar fatura');
return false;
} catch {
setError('Erro de conexão ao deletar fatura');
return false; return false;
} finally { } finally {
setLoading(false); setLoading(false);
@ -160,5 +153,3 @@ export const useFaturas = (): UseFaturasReturn => {
setCurrentPage, setCurrentPage,
}; };
}; };

View file

@ -1,29 +1,29 @@
// @ts-nocheck import { CategoriaDocument, ServiceResponse } from '@/types/legacyEntities';
interface CategoriaData {
export interface CategoriaData {
id?: string; id?: string;
nome?: string; nome?: string;
[key: string]: any;
} }
interface ServiceResponse<T = unknown> { const emptyList = (): ServiceResponse<CategoriaDocument> => ({
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, data: [], documents: [], total: 0 }); const removed = (message = 'Service removido'): ServiceResponse<CategoriaDocument> => ({
const removed = (message = 'Service removido'): ServiceResponse => ({ success: false, error: message, message }); success: false,
error: message,
message,
});
export const categoriaService = { export const categoriaService = {
listarTodas: async () => emptyList<CategoriaData>(), listarTodas: async () => emptyList(),
listar: async (_page = 1, _limit = 10) => emptyList<CategoriaData>(), listar: async (_page = 1, _limit = 10) => emptyList(),
buscarPorNome: async (_nome: string, _page = 1, _limit = 10) => emptyList<CategoriaData>(), buscarPorNome: async (_nome: string, _page = 1, _limit = 10) => emptyList(),
buscarPorId: async (_id?: string) => ({ success: true, data: null as CategoriaData | null }), buscarPorId: async (_id?: string): Promise<ServiceResponse<CategoriaDocument>> => ({ success: true, data: null }),
criar: async (_data?: CategoriaData) => removed(), criar: async (_data?: CategoriaData) => removed(),
atualizar: async (_id?: string, _data?: Partial<CategoriaData>) => removed(), atualizar: async (_id?: string, _data?: Partial<CategoriaData>) => removed(),
deletar: async (_id?: string) => removed(), deletar: async (_id?: string) => removed(),
}; };

View file

@ -1,27 +1,30 @@
interface FaturaData { import { FaturaDocument, ServiceResponse } from '@/types/legacyEntities';
export interface FaturaData {
id?: string; id?: string;
[key: string]: any; nome?: string;
} }
interface ServiceResponse<T = unknown> { const emptyList = (): ServiceResponse<FaturaDocument> => ({
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<FaturaDocument> => ({
const removed = (message = 'Service removido'): ServiceResponse => ({ success: false, error: message, message }); success: false,
error: message,
message,
});
export const faturaService = { export const faturaService = {
listar: async (_page = 1, _limit = 10) => emptyList<FaturaData>(), listar: async (_page = 1, _limit = 10) => emptyList(),
buscarPorNome: async (_nome: string, _page = 1, _limit = 10) => emptyList<FaturaData>(), buscarPorNome: async (_nome: string, _page = 1, _limit = 10) => emptyList(),
criar: async (_data?: FaturaData) => removed(), criar: async (_data?: FaturaData) => removed(),
criarFatura: async (_data?: FaturaData) => removed(), criarFatura: async (_data?: FaturaData) => removed(),
buscarPorUserId: async () => emptyList<FaturaData>(), buscarPorUserId: async () => emptyList(),
buscarPorId: async (_id?: string) => ({ success: true, data: null as FaturaData | null }), buscarPorId: async (_id?: string): Promise<ServiceResponse<FaturaDocument>> => ({ success: true, data: null }),
atualizar: async (_id?: string, _data?: Partial<FaturaData>) => removed(), atualizar: async (_id?: string, _data?: Partial<FaturaData>) => removed(),
deletar: async (_id?: string) => removed(), deletar: async (_id?: string) => removed(),
atualizarFaturaParcial: async (_id?: string, _data?: Partial<FaturaData>) => removed(), atualizarFaturaParcial: async (_id?: string, _data?: Partial<FaturaData>) => removed(),

View file

@ -0,0 +1,23 @@
export interface LegacyDocument {
$id: string;
$createdAt: string;
$updatedAt?: string;
[key: string]: unknown;
}
export interface CategoriaDocument extends LegacyDocument {
nome?: string;
}
export interface FaturaDocument extends LegacyDocument {
nome?: string;
}
export interface ServiceResponse<TDocument> {
success: boolean;
data?: TDocument | null;
documents?: TDocument[];
total?: number;
error?: string;
message?: string;
}