refactor(frontend): type legacy cart management flow

This commit is contained in:
Tiago Yamamoto 2026-03-07 08:17:12 -06:00
parent a1eb0efc72
commit 7e3604ed4a
6 changed files with 158 additions and 144 deletions

View file

@ -1,24 +1,24 @@
'use client';
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { getCurrentUserWithRetry } from '@/lib/appwrite'; // Usar a função com retry
import { getCurrentUserWithRetry } from '@/lib/appwrite';
import { Models } from '@/lib/appwrite';
import { List, Plus } from 'lucide-react';
import Header from '@/components/Header';
import CarrinhoForm from '@/components/CarrinhoForm';
import CarrinhoList from '@/components/CarrinhoList';
import { useCarrinhos, CarrinhoFormData } from '@/hooks/useCarrinhos';
import { CarrinhoDocument } from '@/types/legacyEntities';
const GestaoCarrinhos = () => {
const router = useRouter();
const [user, setUser] = useState<Models.User<Models.Preferences> | null>(null);
const [editing, setEditing] = useState<Models.Document | null>(null);
const [editing, setEditing] = useState<CarrinhoDocument | null>(null);
const [activeTab, setActiveTab] = useState<'lista' | 'cadastro'>('lista');
const [searchTerm, setSearchTerm] = useState('');
const [checkingAuth, setCheckingAuth] = useState(true);
// Mover o hook useCarrinhos para antes da renderização condicional
const {
carrinhos,
loading,
@ -44,10 +44,9 @@ const GestaoCarrinhos = () => {
return;
}
setUser(currentUser);
// Carregar carrinhos apenas após autenticação bem-sucedida
await listarCarrinhos();
} catch (error) {
console.error('❌ Usuário não autenticado:', error);
console.error('Usuário não autenticado:', error);
router.push('/');
} finally {
setCheckingAuth(false);
@ -55,7 +54,7 @@ const GestaoCarrinhos = () => {
};
initializeUser();
}, [router]); // Remover listarCarrinhos das dependências
}, [router, listarCarrinhos]);
const handleFormSubmit = async (formData: CarrinhoFormData): Promise<boolean> => {
if (editing) {
@ -65,17 +64,17 @@ const GestaoCarrinhos = () => {
setActiveTab('lista');
}
return success;
} else {
const success = await cadastrarCarrinho(formData);
if (success) {
setTimeout(() => setActiveTab('lista'), 2000);
}
return success;
}
const success = await cadastrarCarrinho(formData);
if (success) {
setTimeout(() => setActiveTab('lista'), 2000);
}
return success;
};
const handleEdit = (car: Models.Document) => {
setEditing(car);
const handleEdit = (carrinho: CarrinhoDocument) => {
setEditing(carrinho);
setActiveTab('cadastro');
};
@ -94,9 +93,10 @@ const GestaoCarrinhos = () => {
setSearchTerm(term);
if (term.trim()) {
await buscarCarrinhos(term, 1);
} else {
await listarCarrinhos(1);
return;
}
await listarCarrinhos(1);
};
const handleNextPage = () => {
@ -106,24 +106,12 @@ const GestaoCarrinhos = () => {
}
};
// Tela de carregamento durante verificação de autenticação
if (checkingAuth) {
if (checkingAuth || !user) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-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>
<p className="text-gray-600">Verificando autenticação...</p>
</div>
</div>
);
}
if (!user) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-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>
<p className="text-gray-600">Verificando autenticação...</p>
<p className="text-gray-600">Verificando autenticação...</p>
</div>
</div>
);
@ -133,7 +121,7 @@ const GestaoCarrinhos = () => {
<div className="min-h-screen bg-gray-50">
<Header
user={user}
title="Gestão de Carrinhos"
title="Gestão de Carrinhos"
subtitle="Gerencie os carrinhos da plataforma SaveInMed"
/>

View file

@ -1,22 +1,27 @@
import React, { useState, useEffect } from 'react';
import { Models } from '@/lib/appwrite';
import React, { useState, useEffect } from 'react';
import { CarrinhoFormData } from '@/hooks/useCarrinhos';
import { usuarioService } from '@/services/usuarioService';
import { CarrinhoDocument } from '@/types/legacyEntities';
interface CarrinhoFormProps {
onSubmit: (data: CarrinhoFormData) => Promise<boolean>;
onCancel?: () => void;
initialData?: Models.Document | null;
initialData?: CarrinhoDocument | null;
loading?: boolean;
}
interface UsuarioOption {
$id: string;
'nome-civil'?: string;
'nome-social'?: string;
}
const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
onSubmit,
onCancel,
initialData,
loading = false
}) => {
// Inicializar com valores padrão para evitar problemas de input controlado/não controlado
const [formData, setFormData] = useState<CarrinhoFormData>({
usuarios: '',
itens: [],
@ -24,24 +29,21 @@ const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
});
const [itemsText, setItemsText] = useState('');
const [quantText, setQuantText] = useState('');
const [usuariosList, setUsuariosList] = useState<Models.Document[]>([]);
const [usuariosList, setUsuariosList] = useState<UsuarioOption[]>([]);
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
useEffect(() => {
if (initialData) {
const usuarios = (initialData as any).usuarios || '';
const itens = (initialData as any).itens || [];
const quantidade = (initialData as any).quantidade || [];
const usuarios = typeof initialData.usuarios === 'string'
? initialData.usuarios
: initialData.usuarios?.$id || '';
const itens = initialData.itens || [];
const quantidade = Array.isArray(initialData.quantidade) ? initialData.quantidade : [];
setFormData({
usuarios,
itens,
quantidade
});
setFormData({ usuarios, itens, quantidade });
setItemsText(itens.join('\n'));
setQuantText(quantidade.join('\n'));
} else {
// Resetar para valores padrão
setFormData({ usuarios: '', itens: [], quantidade: [] });
setItemsText('');
setQuantText('');
@ -53,10 +55,10 @@ const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
try {
const res = await usuarioService.listar(1, 50);
if (res.success) {
setUsuariosList(res.documents);
setUsuariosList((res.documents || []) as UsuarioOption[]);
}
} catch (error) {
console.error('Erro ao carregar usuários:', error);
console.error('Erro ao carregar usuários:', error);
}
};
loadUsuarios();
@ -76,7 +78,7 @@ const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
if (success) {
setMessage({
type: 'success',
text: initialData ? '🔄 Carrinho atualizado com sucesso!' : '🎉 Carrinho cadastrado com sucesso!'
text: initialData ? 'Carrinho atualizado com sucesso!' : 'Carrinho cadastrado com sucesso!'
});
if (!initialData) {
@ -111,7 +113,7 @@ const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
return (
<div className="max-w-2xl mx-auto bg-white p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-gray-800">
{initialData ? '✏️ Editar Carrinho' : 'Cadastrar Novo Carrinho'}
{initialData ? 'Editar Carrinho' : 'Cadastrar Novo Carrinho'}
</h2>
{message && (
@ -127,20 +129,20 @@ const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="usuarios" className="block text-sm font-medium text-gray-700 mb-2">
Usuário *
Usuário *
</label>
<select
id="usuarios"
name="usuarios"
value={formData.usuarios} // Sempre controlado
value={formData.usuarios}
onChange={handleInputChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">Selecione um usuário</option>
<option value="">Selecione um usuário</option>
{usuariosList.map((usuario) => (
<option key={usuario.$id} value={usuario.$id}>
{(usuario as any)['nome-civil'] || (usuario as any)['nome-social'] || usuario.$id}
{usuario['nome-civil'] || usuario['nome-social'] || usuario.$id}
</option>
))}
</select>
@ -153,7 +155,7 @@ const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
<textarea
id="itens"
name="itens"
value={itemsText} // Sempre controlado
value={itemsText}
onChange={handleInputChange}
rows={4}
required
@ -169,7 +171,7 @@ const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
<textarea
id="quantidade"
name="quantidade"
value={quantText} // Sempre controlado
value={quantText}
onChange={handleInputChange}
rows={4}
required
@ -190,7 +192,7 @@ const CarrinhoForm: React.FC<CarrinhoFormProps> = ({
{initialData ? 'Atualizando...' : 'Cadastrando...'}
</div>
) : (
initialData ? '🔄 Atualizar Carrinho' : 'Cadastrar Carrinho'
initialData ? 'Atualizar Carrinho' : 'Cadastrar Carrinho'
)}
</button>

View file

@ -1,22 +1,21 @@
import React from 'react';
import { Models } from '@/lib/appwrite';
import React from 'react';
import SearchBar from './SearchBar';
import RefreshButton from './RefreshButton';
import ListHeader from './ListHeader';
import DataTable, { Column } from './DataTable';
import Pagination from './Pagination';
import ActionButton from './ActionButton';
import TableActions from './TableActions'; // Adicionar esta importação
import TableActions from './TableActions';
import { CarrinhoDocument } from '@/types/legacyEntities';
interface Props {
carrinhos: Models.Document[];
carrinhos: CarrinhoDocument[];
loading: boolean;
isChangingPage?: boolean;
error: string | null;
totalCarrinhos: number;
currentPage: number;
pageSize: number;
onEdit: (doc: Models.Document) => void;
onEdit: (doc: CarrinhoDocument) => void;
onDelete: (id: string) => Promise<boolean>;
onRefresh: () => void;
onPrevPage: () => void;
@ -24,16 +23,14 @@ interface Props {
onSearch: (usuario: string) => void;
}
const columns: Column<Models.Document>[] = [
const columns: Column<CarrinhoDocument>[] = [
{
key: 'usuarios',
header: 'Usuário',
header: 'Usuário',
render: row => {
const usuario = (row as any).usuarios;
const usuario = row.usuarios;
if (usuario && typeof usuario === 'object') {
return (
usuario['nome-civil'] || usuario['nome-social'] || usuario.$id || ''
);
return usuario['nome-civil'] || usuario['nome-social'] || usuario.$id || '';
}
return usuario;
}
@ -41,14 +38,14 @@ const columns: Column<Models.Document>[] = [
{
key: 'itens',
header: 'Itens',
render: row => ((row as any).itens || []).join(', ')
render: row => (row.itens || []).join(', ')
},
{
key: 'quantidades',
header: 'Quantidades',
className: 'text-center',
render: row => {
const quantidades = (row as any).quantidades;
const quantidades = row.quantidades;
const valor = Array.isArray(quantidades)
? quantidades.join(', ')
: typeof quantidades === 'number'
@ -93,7 +90,7 @@ const CarrinhoList: React.FC<Props> = ({
value={search}
onChange={setSearch}
onSearch={handleSearch}
placeholder="Buscar usuário"
placeholder="Buscar usuário"
/>
<RefreshButton onClick={onRefresh} loading={loading} />
</ListHeader>
@ -108,7 +105,7 @@ const CarrinhoList: React.FC<Props> = ({
<div className="bg-blue-50 border border-blue-200 rounded-md p-3 mb-4">
<div className="flex items-center">
<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>
)}

View file

@ -1,7 +1,6 @@
// @ts-nocheck
import { useState, useCallback, useRef, useEffect } from 'react';
import { Models } from '@/lib/appwrite';
import { carrinhoService } from '@/services/carrinhoService';
import { CarrinhoDocument } from '@/types/legacyEntities';
export interface CarrinhoFormData {
usuarios: string;
@ -10,7 +9,7 @@ export interface CarrinhoFormData {
}
export interface UseCarrinhosReturn {
carrinhos: Models.Document[];
carrinhos: CarrinhoDocument[];
loading: boolean;
isChangingPage: boolean;
isCreating: boolean;
@ -30,7 +29,7 @@ export interface UseCarrinhosReturn {
const PAGE_SIZE = 10;
export const useCarrinhos = (): UseCarrinhosReturn => {
const [carrinhos, setCarrinhos] = useState<Models.Document[]>([]);
const [carrinhos, setCarrinhos] = useState<CarrinhoDocument[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [totalCarrinhos, setTotalCarrinhos] = useState(0);
@ -40,11 +39,10 @@ export const useCarrinhos = (): UseCarrinhosReturn => {
const [searchTerm, setSearchTerm] = useState('');
const isInitialMount = useRef(true);
const listarCarrinhos = useCallback(
async (page = currentPage, search = searchTerm) => {
setIsChangingPage(true);
setLoading(true);
setError(null);
const listarCarrinhos = useCallback(async (page = currentPage, search = searchTerm) => {
setIsChangingPage(true);
setLoading(true);
setError(null);
try {
const response = search.trim()
@ -56,47 +54,42 @@ export const useCarrinhos = (): UseCarrinhosReturn => {
setTotalCarrinhos(response.total || 0);
setCurrentPage(page);
setSearchTerm(search);
} else {
setError(response.error || 'Erro ao carregar carrinhos');
return;
}
} catch (err) {
setError('Erro de conexão ao carregar carrinhos');
setError(response.error || 'Erro ao carregar carrinhos');
} catch {
setError('Erro de conexão ao carregar carrinhos');
} finally {
setLoading(false);
setIsChangingPage(false);
}
}, [currentPage, searchTerm]);
const buscarCarrinhos = useCallback(
async (usuario: string, page = 1) => {
setIsChangingPage(true);
setLoading(true);
setError(null);
const buscarCarrinhos = useCallback(async (usuario: string, page = 1) => {
setIsChangingPage(true);
setLoading(true);
setError(null);
try {
const response = await carrinhoService.buscarPorUsuario(
usuario,
page,
PAGE_SIZE
);
try {
const response = await carrinhoService.buscarPorUsuario(usuario, page, PAGE_SIZE);
if (response.success) {
setCarrinhos(response.documents || []);
setTotalCarrinhos(response.total || 0);
setCurrentPage(page);
setSearchTerm(usuario);
} else {
setError(response.error || 'Erro ao buscar carrinhos');
}
} catch (err) {
setError('Erro de conexão ao buscar carrinhos');
} finally {
setLoading(false);
setIsChangingPage(false);
if (response.success) {
setCarrinhos(response.documents || []);
setTotalCarrinhos(response.total || 0);
setCurrentPage(page);
setSearchTerm(usuario);
return;
}
},
[]
);
setError(response.error || 'Erro ao buscar carrinhos');
} catch {
setError('Erro de conexão ao buscar carrinhos');
} finally {
setLoading(false);
setIsChangingPage(false);
}
}, []);
const cadastrarCarrinho = useCallback(async (formData: CarrinhoFormData): Promise<boolean> => {
setIsCreating(true);
@ -108,12 +101,12 @@ export const useCarrinhos = (): UseCarrinhosReturn => {
if (response.success) {
await listarCarrinhos(1, searchTerm);
return true;
} else {
setError(response.error || 'Erro ao cadastrar carrinho');
return false;
}
} catch (err) {
setError('Erro de conexão ao cadastrar carrinho');
setError(response.error || 'Erro ao cadastrar carrinho');
return false;
} catch {
setError('Erro de conexão ao cadastrar carrinho');
return false;
} finally {
setIsCreating(false);
@ -130,17 +123,17 @@ export const useCarrinhos = (): UseCarrinhosReturn => {
if (response.success) {
await listarCarrinhos(currentPage, searchTerm);
return true;
} else {
setError(response.error || 'Erro ao atualizar carrinho');
return false;
}
} catch (err) {
setError('Erro de conexão ao atualizar carrinho');
setError(response.error || 'Erro ao atualizar carrinho');
return false;
} catch {
setError('Erro de conexão ao atualizar carrinho');
return false;
} finally {
setLoading(false);
}
}, [currentPage, listarCarrinhos]);
}, [currentPage, listarCarrinhos, searchTerm]);
const deletarCarrinho = useCallback(async (id: string): Promise<boolean> => {
setLoading(true);
@ -152,17 +145,17 @@ export const useCarrinhos = (): UseCarrinhosReturn => {
if (response.success) {
await listarCarrinhos(currentPage, searchTerm);
return true;
} else {
setError(response.error || 'Erro ao deletar carrinho');
return false;
}
} catch (err) {
setError('Erro de conexão ao deletar carrinho');
setError(response.error || 'Erro ao deletar carrinho');
return false;
} catch {
setError('Erro de conexão ao deletar carrinho');
return false;
} finally {
setLoading(false);
}
}, [currentPage, listarCarrinhos]);
}, [currentPage, listarCarrinhos, searchTerm]);
useEffect(() => {
if (isInitialMount.current) {
@ -172,9 +165,10 @@ export const useCarrinhos = (): UseCarrinhosReturn => {
if (searchTerm.trim()) {
buscarCarrinhos(searchTerm, currentPage);
} else {
listarCarrinhos(currentPage);
return;
}
listarCarrinhos(currentPage);
}, [currentPage, searchTerm, listarCarrinhos, buscarCarrinhos]);
return {
@ -192,7 +186,6 @@ export const useCarrinhos = (): UseCarrinhosReturn => {
deletarCarrinho,
setCurrentPage,
searchTerm,
setSearchTerm
setSearchTerm,
};
};

View file

@ -1,8 +1,31 @@
// Service stub - removido durante migração para BFF
import { CarrinhoDocument, ServiceResponse } from '@/types/legacyEntities';
export interface CarrinhoData {
usuarios?: string;
itens?: string[];
quantidade?: number[];
}
const emptyList = (): ServiceResponse<CarrinhoDocument> => ({
success: true,
data: null,
documents: [],
total: 0,
});
const removed = (message = 'Service removido'): ServiceResponse<CarrinhoDocument> => ({
success: false,
error: message,
message,
});
export const carrinhoService = {
listarPorUsuario: async () => ({ data: [], total: 0 }),
buscarPorId: async () => ({ data: null }),
criar: async () => ({ success: false, message: 'Service removido' }),
atualizar: async () => ({ success: false, message: 'Service removido' }),
excluir: async () => ({ success: false, message: 'Service removido' })
listar: async (_page = 1, _limit = 10) => emptyList(),
buscarPorUsuario: async (_usuario = '', _page = 1, _limit = 10) => emptyList(),
listarPorUsuario: async (_usuario = '', _page = 1, _limit = 10) => emptyList(),
buscarPorId: async (_id?: string): Promise<ServiceResponse<CarrinhoDocument>> => ({ success: true, data: null }),
criar: async (_data?: CarrinhoData) => removed(),
atualizar: async (_id?: string, _data?: Partial<CarrinhoData>) => removed(),
deletar: async (_id?: string) => removed(),
excluir: async (_id?: string) => removed(),
};

View file

@ -29,11 +29,22 @@ export interface CatalogoProdutoDocument extends LegacyDocument {
'preco-atual'?: number;
'preco-fabrica'?: number;
'preco-nf'?: number;
'preco-unitario'?: number;
pmc?: number;
quantidade?: number;
estoque?: number;
laboratorio?: string;
categoria?: string;
subcategoria?: string;
laboratorios?: Array<{ $id?: string; nome?: string } | string>;
categorias?: { nome?: string } | null;
}
export interface CarrinhoDocument extends LegacyDocument {
usuarios?: string | { $id?: string; 'nome-civil'?: string; 'nome-social'?: string } | null;
itens?: string[];
quantidade?: number[];
quantidades?: number[] | number;
}
export interface ServiceResponse<TDocument> {