Painel de Administração em Tempo Real CRUD com React Native e Firebase Firestore

No desenvolvimento de aplicativos modernos de e-commerce ou catálogo, criar uma interface administrativa eficiente para gerenciar o inventário é essencial. Este artigo desmembra o funcionamento do componente TelaAdm, uma solução prática em React Native projetada para realizar todas as operações de um CRUD (Criar, Ler, Atualizar e Deletar) integrado ao banco de dados NoSQL Firebase Firestore.

Além de gerenciar os dados, o código se destaca por ser híbrido (adaptando-se para Web, iOS e Android) e por utilizar atualizações em tempo real. Se você está construindo um ecossistema completo, esta tela de administração complementa perfeitamente recursos avançados como a autenticação com Firebase no React Native Expo para restringir o acesso apenas a usuários administradores.

image Painel de Administração em Tempo Real CRUD com React Native e Firebase Firestore

1. Arquitetura Geral e Estrutura de Estados

O componente utiliza o ecossistema modular do Firebase e os Hooks do React para criar uma interface reativa e performática.

Para controlar o comportamento da tela, o componente monitora os seguintes estados através do hook useState:

  • produtos: Um array que armazena a lista de itens vindos do banco de dados.
  • carregando e salvando: Booleans que controlam os indicadores de progresso (ActivityIndicator), melhorando a experiência do usuário (UX).
  • novoProduto: Objeto que armazena os valores digitados no formulário em tempo real.
  • editandoId: Armazena o ID do produto que está sendo modificado. Se for null, o sistema entende que o usuário está criando um novo produto.

2. Como Funcionam as Operações do CRUD

Abaixo, detalhamos o fluxo lógico e técnico de como os dados são manipulados entre o aplicativo e os servidores do Firebase.

C – Cadastrar um Novo Produto (Create)

image-1-1024x518 Painel de Administração em Tempo Real CRUD com React Native e Firebase Firestore

A ação de cadastro ocorre dentro da função salvarProduto(), que é disparada quando o usuário clica no botão “Salvar Produto no Firestore”.

  1. Validação: O código verifica se os campos obrigatórios (Produto e Preço) foram preenchidos usando .trim() (que remove espaços em branco antes e depois do texto). Se estiverem vazios, ele exibe um alerta e interrompe a execução.
  2. Definição de Imagem Padrão: O código verifica se o campo Foto está vazio. Se estiver, ele define automaticamente uma URL padrão do Unsplash para evitar que o produto fique sem imagem na interface.
  3. Fluxo do Cadastro: Como o estado editandoId está como null, o programa cria uma referência para a coleção e envia os dados:JavaScriptconst produtosRef = collection(bancoDados, 'produtos'); await addDoc(produtosRef, { ...novoProduto, Foto: fotoFinal, Foto2: foto2Final, Foto3: foto3Final, createdAt: new Date(), // Registra a data de criação });
  4. Finalização: Após a gravação, o formulário é limpo (voltando aos camposIniciais), o formulário é ocultado e um alerta de sucesso é exibido.

R – Listagem em Tempo Real (Read)

A busca de dados não é feita através de uma requisição estática (como um fetch comum). O código utiliza o método onSnapshot do Firestore dentro de um useEffect:

JavaScript

useEffect(() => {
  const produtosRef = collection(bancoDados, 'produtos');
  const desinscrever = onSnapshot(produtosRef, (querySnapshot) => {
    const lista = [];
    querySnapshot.forEach((docSnap) => {
      lista.push({ id: docSnap.id, ...docSnap.data() });
    });
    setProdutos(lista);
    setCarregando(false);
  });

  return desinscrever; // Limpeza de memória ao sair da tela
}, []);

O onSnapshot funciona como um canal de comunicação aberto (Websocket) com o Firestore. Sempre que qualquer alteração for feita na coleção diretamente no Console do Firebase, o Firestore avisa o aplicativo e a tela se atualiza automaticamente, sem necessidade de recarregar.

Os dados recebidos são repassados para uma lista em malha (grid) através da propriedade numColumns={2}. Para entender a fundo como essa renderização funciona e como otimizar o desempenho do seu app, vale a pena ler o artigo sobre trabalhando com listas e o poder da FlatList no React Native, que detalha a paginação e consumo de memória desse componente.

U – Editar um Produto Existente (Update)

A edição é dividida em duas etapas: preparação e atualização.

  1. Preparação: Quando o usuário clica no botão “✏️ Editar”, a função iniciarEdicao(produto) é chamada. Ela salva o ID do produto selecionado no estado editandoId (setEditandoId(produto.id)) e preenche o estado novoProduto com todos os dados atuais dele, fazendo com que o formulário abra preenchido.
  2. Atualização: Quando o usuário clica em “Salvar Alterações”, a função salvarProduto() é acionada novamente. Como editandoId agora possui um ID, o código segue o caminho do updateDoc:JavaScriptconst produtoRef = doc(bancoDados, 'produtos', editandoId); // Aponta para o documento específico await updateDoc(produtoRef, { ...novoProduto, Foto: fotoFinal, updatedAt: new Date(), // Registra a data de classificação });

D – Excluir um Produto (Delete)

A função deletarProduto invoca o método deleteDoc(doc(bancoDados, 'produtos', id)). Ela possui uma validação de plataforma: no ambiente Web, usa o nativo window.confirm, enquanto no Mobile exibe o componente Alert.alert com um botão estilizado como destructive.

3. Tratamento e Manipulação de Dados

Os diferentes tipos de dados inseridos pelos usuários recebem tratamentos específicos para evitar erros e manter a consistência do banco de dados:

  • Campos Alfanuméricos (Texto): Campos como Produto e Descrição usam strings comuns e passam por .trim() para limpar espaços desnecessários digitados por engano.
  • Links de Fotos (URLs): São salvos no banco como strings normais. O código aceita qualquer link web válido e aplica uma imagem genérica caso o campo principal seja deixado em branco. Se você deseja evoluir esse painel e permitir que o usuário faça o upload do arquivo de imagem direto do celular em vez de colar um link, confira o passo a passo de como criar uma tela de perfil completa no React Native com Firebase, AsyncStorage e upload de foto, onde o processo de armazenamento de arquivos (Storage) é detalhado.
  • Campos Numéricos (Preços e Descontos): No formulário, o uso de keyboardType="numeric" força o celular a abrir o teclado numérico. Porém, como o componente TextInput do React Native sempre retorna uma string, o tratamento real ocorre na função formatarMoeda(). O código substitui eventuais vírgulas por pontos (.toString().replace(',', '.')) e usa o parseFloat() para converter o texto em um número decimal real antes de realizar cálculos matemáticos ou exibi-los formatados com o padrão brasileiro (R$ X.XXX,XX).

4. Recursos de Interface e UX

  • Cálculo Dinâmico de Desconto: Se o administrador preencher o ValorNormal e o Preço de venda, mas deixar o campo Desconto vazio, a função renderItem calcula matematicamente a diferença percentual e exibe automaticamente uma etiqueta vermelha (ex: 20% OFF).
  • Dados de Demonstração: Se o banco de dados estiver completamente vazio na sua conta do Firebase Console, a interface exibe o botão “✨ Gerar Produtos de Exemplo”. Ele dispara a função popularProdutosPadrao(), que faz um loop inserindo quatro produtos eletrônicos fictícios no Firestore para facilitar os testes de layout.
  • Adaptação Multiplataforma: O código usa a API Platform.OS para alternar entre os alertas nativos do navegador (window.alert / window.confirm) e os alertas do celular (Alert.alert), garantindo que o app funcione sem quebras tanto na Web quanto no Mobile.

5. 📚 Glossário de Comandos Utilizados

⚛️ React & React Native (Interface e Ciclo de Vida)

  • useState: Hook do React que cria variáveis de estado reativas. Quando o valor de um estado muda, a interface é atualizada automaticamente.
  • useEffect: Hook usado para executar efeitos colaterais. No código, ele ativa o ouvinte do banco de dados ao carregar a tela e o desativa quando o usuário sai dela.
  • FlatList: Componente otimizado para renderizar listas. Ele economiza memória processando apenas os itens que estão atualmente visíveis na tela.
  • TextInput: Campo de texto padrão que permite a inserção de dados pelo usuário.
  • TouchableOpacity: Um componente de botão que reduz sua opacidade ao ser tocado, oferecendo um feedback visual de clique.
  • ActivityIndicator: Ícone circular de carregamento (spinner), usado para indicar que uma operação assíncrona está em andamento.
  • KeyboardAvoidingView: Componente que move a tela para cima quando o teclado do celular é aberto, impedindo que os campos de digitação fiquem cobertos.
  • Platform.OS: Propriedade que detecta o sistema operacional atual (ios, android ou web) para aplicar regras de comportamento específicas.

🔥 Firebase Firestore (Banco de Dados NoSQL)

  • bancoDados: A instância de configuração que conecta o aplicativo ao seu projeto específico no Firebase.
  • collection(): Função que define uma referência a uma coleção do Firestore (equivalente a uma tabela em bancos relacionais). No código, aponta para 'produtos'.
  • doc(): Função que define uma referência a um documento específico dentro de uma coleção, utilizando o seu ID único.
  • addDoc(): Método assíncrono que insere um novo documento dentro de uma coleção. O Firestore gera automaticamente um ID alfanumérico único para esse registro.
  • updateDoc(): Método utilizado para modificar apenas propriedades específicas de um documento já existente, sem sobrescrever os demais campos.
  • deleteDoc(): Método que remove permanentemente um documento específico do banco de dados.
  • onSnapshot(): Ouvinte ativo em tempo real. Ele mantém o aplicativo conectado ao banco e dispara uma função de atualização sempre que houver qualquer modificação nos dados.
  • querySnapshot: O objeto retornado pelo Firebase que contém os dados brutos encontrados dentro da coleção consultada.
  • docSnap.data(): Função interna do Firebase usada para extrair as propriedades e os valores reais salvos dentro de um documento recuperado.

Prompt Sugerido (Toque para selecionar)
import React, { useEffect, useState } from 'react'; import { View, Text, TextInput, TouchableOpacity, Image, StyleSheet, FlatList, ActivityIndicator, Alert, KeyboardAvoidingView, Platform, } from 'react-native'; import { bancoDados } from '../config/firebaseConfig'; import { collection, onSnapshot, addDoc, doc, deleteDoc, updateDoc } from 'firebase/firestore';const camposIniciais = { Produto: '', Preço: '', Descrição: '', Foto: '', Foto2: '', Foto3: '', ValorNormal: '', ValorDesconto: '', Desconto: '', };export default function TelaAdm() { const [produtos, setProdutos] = useState([]); const [carregando, setCarregando] = useState(true); const [salvando, setSalvando] = useState(false); const [novoProduto, setNovoProduto] = useState(camposIniciais); const [mostrarFormulario, setMostrarFormulario] = useState(false); const [editandoId, setEditandoId] = useState(null);// Buscar produtos em tempo real do Firestore useEffect(() => { const produtosRef = collection(bancoDados, 'produtos'); const desinscrever = onSnapshot(produtosRef, (querySnapshot) => { const lista = []; querySnapshot.forEach((docSnap) => { lista.push({ id: docSnap.id, ...docSnap.data() }); }); setProdutos(lista); setCarregando(false); }, (erro) => { console.error("Erro ao buscar produtos:", erro); Alert.alert("Erro", "Não foi possível carregar os produtos."); setCarregando(false); });return desinscrever; }, []);// Popular com produtos padrão se estiver vazio const popularProdutosPadrao = async () => { setCarregando(true); try { const produtosRef = collection(bancoDados, 'produtos'); const produtosExemplo = [ { Produto: 'Smartphone Quantum X', Preço: '1599.90', Descrição: 'Smartphone com tela AMOLED de 6.7 polegadas, 128GB de armazenamento e câmera quádrupla de 64MP.', Foto: 'https://images.unsplash.com/photo-1511707171634-5f897ff02aa9?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3', ValorNormal: '1999.90', ValorDesconto: '400.00', Desconto: '20%', }, { Produto: 'Notebook Gamer Pro', Preço: '4599.00', Descrição: 'Processador de última geração, 16GB RAM, SSD 512GB e placa de vídeo dedicada de 4GB.', Foto: 'https://images.unsplash.com/photo-1496181130204-755241544e3f?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3', ValorNormal: '5499.00', ValorDesconto: '900.00', Desconto: '16%', }, { Produto: 'Fone Noise Cancelling', Preço: '299.90', Descrição: 'Fones de ouvido over-ear sem fio com cancelamento de ruído ativo e bateria de 40 horas.', Foto: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3', ValorNormal: '499.90', ValorDesconto: '200.00', Desconto: '40%', }, { Produto: 'Smartwatch Sport Fit', Preço: '189.90', Descrição: 'Monitor de ritmo cardíaco, GPS integrado, resistente à água e múltiplos modos esportivos.', Foto: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3', ValorNormal: '269.90', ValorDesconto: '80.00', Desconto: '30%', } ];for (const prod of produtosExemplo) { await addDoc(produtosRef, prod); } Alert.alert('Sucesso', 'Produtos padrão criados no Firestore para demonstração!'); } catch (erro) { console.error("Erro ao popular produtos:", erro); Alert.alert('Erro', 'Falha ao gerar produtos padrão.'); } finally { setCarregando(false); } };const atualizarCampo = (campo, valor) => { setNovoProduto((anterior) => ({ ...anterior, [campo]: valor })); };const toggleFormulario = () => { if (mostrarFormulario) { setNovoProduto(camposIniciais); setEditandoId(null); } setMostrarFormulario(!mostrarFormulario); };const iniciarEdicao = (produto) => { setEditandoId(produto.id); setNovoProduto({ Produto: produto.Produto || '', Preço: produto.Preço || '', Descrição: produto.Descrição || '', Foto: produto.Foto || '', Foto2: produto.Foto2 || '', Foto3: produto.Foto3 || '', ValorNormal: produto.ValorNormal || '', ValorDesconto: produto.ValorDesconto || '', Desconto: produto.Desconto || '', }); setMostrarFormulario(true); };const salvarProduto = async () => { const { Produto, Preço } = novoProduto; if (!Produto.trim() || !Preço.trim()) { Alert.alert('Atenção', 'Nome do produto e Preço são obrigatórios.'); return; }setSalvando(true); try { const fotoFinal = novoProduto.Foto.trim() ? novoProduto.Foto : 'https://images.unsplash.com/photo-1531403009284-440f080d1e12?w=500&auto=format&fit=crop&q=60'; const foto2Final = novoProduto.Foto2.trim(); const foto3Final = novoProduto.Foto3.trim();if (editandoId) { // Atualizar produto existente const produtoRef = doc(bancoDados, 'produtos', editandoId); await updateDoc(produtoRef, { ...novoProduto, Foto: fotoFinal, Foto2: foto2Final, Foto3: foto3Final, updatedAt: new Date(), }); if (Platform.OS === 'web') { window.alert('Sucesso: Produto atualizado com sucesso!'); } else { Alert.alert('Sucesso', 'Produto atualizado com sucesso!'); } } else { // Cadastrar novo produto const produtosRef = collection(bancoDados, 'produtos'); await addDoc(produtosRef, { ...novoProduto, Foto: fotoFinal, Foto2: foto2Final, Foto3: foto3Final, createdAt: new Date(), }); if (Platform.OS === 'web') { window.alert('Sucesso: Produto cadastrado com sucesso!'); } else { Alert.alert('Sucesso', 'Produto cadastrado com sucesso!'); } }setNovoProduto(camposIniciais); setEditandoId(null); setMostrarFormulario(false); } catch (erro) { console.error("Erro ao salvar produto:", erro); if (Platform.OS === 'web') { window.alert('Erro: Não foi possível salvar o produto.'); } else { Alert.alert('Erro', 'Não foi possível salvar o produto.'); } } finally { setSalvando(false); } };const deletarProduto = (id, nome) => { const acaoExcluir = async () => { try { await deleteDoc(doc(bancoDados, 'produtos', id)); if (Platform.OS === 'web') { window.alert('Sucesso: Produto removido com sucesso.'); } else { Alert.alert('Sucesso', 'Produto removido com sucesso.'); } } catch (erro) { console.error("Erro ao deletar produto:", erro); if (Platform.OS === 'web') { window.alert('Erro: Não foi possível remover o produto.'); } else { Alert.alert('Erro', 'Não foi possível remover o produto.'); } } };if (Platform.OS === 'web') { const confirmou = window.confirm(`Deseja realmente deletar o produto "${nome}"?`); if (confirmou) { acaoExcluir(); } } else { Alert.alert( 'Confirmar Exclusão', `Deseja realmente deletar o produto "${nome}"?`, [ { text: 'Cancelar', style: 'cancel' }, { text: 'Excluir', style: 'destructive', onPress: acaoExcluir } ] ); } };const formatarMoeda = (valor) => { if (!valor) return 'R$ 0,00'; const num = parseFloat(valor.toString().replace(',', '.')); if (isNaN(num)) return `R$ ${valor}`; return `R$ ${num.toFixed(2).replace('.', ',')}`; };const renderItem = ({ item }) => { // Calcular desconto percentual se não estiver preenchido mas termos ValorNormal e Preço let descontoTexto = item.Desconto; if (!descontoTexto && item.ValorNormal && item.Preço) { const normal = parseFloat(item.ValorNormal.toString().replace(',', '.')); const preco = parseFloat(item.Preço.toString().replace(',', '.')); if (!isNaN(normal) && !isNaN(preco) && normal > preco) { const perc = Math.round(((normal - preco) / normal) * 100); descontoTexto = `${perc}%`; } }return ( <View style={estilos.card}> <Image source={{ uri: item.Foto }} style={estilos.imagemProduto} resizeMode="cover" />{/* Botão de excluir no topo esquerdo */} <TouchableOpacity style={estilos.botaoDeletar} onPress={() => deletarProduto(item.id, item.Produto)} > <Text style={estilos.textoBotaoDeletar}>🗑️</Text> </TouchableOpacity>{/* Badge de desconto no topo direito */} {descontoTexto ? ( <View style={estilos.badgeDesconto}> <Text style={estilos.textoBadgeDesconto}>{descontoTexto} OFF</Text> </View> ) : null} <View style={estilos.infoContainer}> <Text numberOfLines={2} style={estilos.nomeProduto}>{item.Produto}</Text> {item.Descrição ? ( <Text numberOfLines={1} style={estilos.descricaoProduto}>{item.Descrição}</Text> ) : null}<View style={estilos.precosContainer}> {item.ValorNormal ? ( <Text style={estilos.precoNormal}>{formatarMoeda(item.ValorNormal)}</Text> ) : null} <Text style={estilos.precoVenda}>{formatarMoeda(item.Preço)}</Text>{item.ValorDesconto ? ( <Text style={estilos.valorDescontoDescricao}> Economize {formatarMoeda(item.ValorDesconto)} </Text> ) : null} </View><TouchableOpacity style={estilos.botaoEditar} onPress={() => iniciarEdicao(item)}> <Text style={estilos.textoBotaoEditar}>✏️ Editar</Text> </TouchableOpacity> </View> </View> ); };return ( <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={estilos.container} > <View style={estilos.header}> <Text style={estilos.subtitulo}> {produtos.length} {produtos.length === 1 ? 'produto encontrado' : 'produtos encontrados'} </Text> <TouchableOpacity style={estilos.botaoToggleForm} onPress={toggleFormulario} > <Text style={estilos.textoBotaoToggleForm}> {mostrarFormulario ? (editandoId ? 'Fechar Edição ✖' : 'Fechar Cadastro ✖') : 'Novo Produto ➕'} </Text> </TouchableOpacity> </View>{mostrarFormulario && ( <View style={estilos.formulario}> <Text style={estilos.tituloFormulario}> {editandoId ? 'Editar Produto' : 'Cadastrar Novo Produto'} </Text> <TextInput placeholder="Nome do Produto *" style={estilos.input} value={novoProduto.Produto} onChangeText={(v) => atualizarCampo('Produto', v)} /><TextInput placeholder="Descrição (Opcional)" style={estilos.input} value={novoProduto.Descrição} onChangeText={(v) => atualizarCampo('Descrição', v)} /><View style={estilos.inputLinha}> <TextInput placeholder="Preço Venda (ex: 89.90) *" keyboardType="numeric" style={[estilos.input, { flex: 1, marginRight: 8 }]} value={novoProduto.Preço} onChangeText={(v) => atualizarCampo('Preço', v)} /> <TextInput placeholder="Preço Normal (ex: 120.00)" keyboardType="numeric" style={[estilos.input, { flex: 1 }]} value={novoProduto.ValorNormal} onChangeText={(v) => atualizarCampo('ValorNormal', v)} /> </View><View style={estilos.inputLinha}> <TextInput placeholder="Valor Desconto R$" keyboardType="numeric" style={[estilos.input, { flex: 1, marginRight: 8 }]} value={novoProduto.ValorDesconto} onChangeText={(v) => atualizarCampo('ValorDesconto', v)} /> <TextInput placeholder="Desconto (ex: 25%)" style={[estilos.input, { flex: 1 }]} value={novoProduto.Desconto} onChangeText={(v) => atualizarCampo('Desconto', v)} /> </View><TextInput placeholder="URL da Foto Principal (Opcional)" style={estilos.input} value={novoProduto.Foto} onChangeText={(v) => atualizarCampo('Foto', v)} /><TextInput placeholder="URL da Foto 2 (Opcional)" style={estilos.input} value={novoProduto.Foto2} onChangeText={(v) => atualizarCampo('Foto2', v)} /><TextInput placeholder="URL da Foto 3 (Opcional)" style={estilos.input} value={novoProduto.Foto3} onChangeText={(v) => atualizarCampo('Foto3', v)} /><TouchableOpacity style={[estilos.botaoSalvar, salvando && estilos.botaoDesativado]} onPress={salvarProduto} disabled={salvando} > {salvando ? ( <ActivityIndicator color="#fff" /> ) : ( <Text style={estilos.textoBotaoSalvar}> {editandoId ? 'Salvar Alterações' : 'Salvar Produto no Firestore'} </Text> )} </TouchableOpacity> </View> )}{carregando ? ( <View style={estilos.centralizado}> <ActivityIndicator size="large" color="#007BFF" /> <Text style={estilos.textoCarregando}>Carregando produtos...</Text> </View> ) : produtos.length === 0 ? ( <View style={estilos.semProdutos}> <Text style={estilos.textoSemProdutos}>Nenhum produto cadastrado no Firestore.</Text> <TouchableOpacity style={estilos.botaoPopular} onPress={popularProdutosPadrao}> <Text style={estilos.textoBotaoPopular}>✨ Gerar Produtos de Exemplo</Text> </TouchableOpacity> </View> ) : ( <FlatList data={produtos} keyExtractor={(item) => item.id} renderItem={renderItem} numColumns={2} columnWrapperStyle={estilos.linhaGrid} contentContainerStyle={estilos.lista} /> )} </KeyboardAvoidingView> ); }const estilos = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f7fa', }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: '#e1e8ed', }, subtitulo: { fontSize: 14, color: '#657786', fontWeight: '600', }, botaoToggleForm: { backgroundColor: '#007BFF', paddingVertical: 6, paddingHorizontal: 12, borderRadius: 20, }, textoBotaoToggleForm: { color: '#fff', fontSize: 12, fontWeight: 'bold', }, formulario: { backgroundColor: '#fff', padding: 16, borderBottomWidth: 1, borderBottomColor: '#e1e8ed', }, tituloFormulario: { fontSize: 16, fontWeight: 'bold', marginBottom: 12, color: '#14171a', }, input: { backgroundColor: '#f8f9fa', borderWidth: 1, borderColor: '#e1e8ed', borderRadius: 8, padding: 10, marginBottom: 10, fontSize: 14, }, inputLinha: { flexDirection: 'row', justifyContent: 'space-between', }, botaoSalvar: { backgroundColor: '#28a745', padding: 12, borderRadius: 8, alignItems: 'center', marginTop: 6, }, botaoDesativado: { backgroundColor: '#94d3a2', }, textoBotaoSalvar: { color: '#fff', fontWeight: 'bold', fontSize: 14, }, centralizado: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, textoCarregando: { marginTop: 10, color: '#657786', fontSize: 14, }, semProdutos: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 40, }, textoSemProdutos: { fontSize: 16, color: '#657786', textAlign: 'center', marginBottom: 20, }, botaoPopular: { backgroundColor: '#007BFF', paddingVertical: 12, paddingHorizontal: 20, borderRadius: 25, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, textoBotaoPopular: { color: '#fff', fontSize: 14, fontWeight: 'bold', }, lista: { padding: 8, }, linhaGrid: { justifyContent: 'space-between', }, card: { backgroundColor: '#fff', width: '48%', borderRadius: 12, marginBottom: 16, overflow: 'hidden', borderWidth: 1, borderColor: '#e1e8ed', elevation: 3, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 8, position: 'relative', }, botaoDeletar: { position: 'absolute', top: 8, left: 8, zIndex: 10, backgroundColor: 'rgba(255, 255, 255, 0.9)', width: 28, height: 28, borderRadius: 14, justifyContent: 'center', alignItems: 'center', borderWidth: 1, borderColor: '#e1e8ed', }, textoBotaoDeletar: { fontSize: 12, }, badgeDesconto: { position: 'absolute', top: 8, right: 8, zIndex: 10, backgroundColor: '#e0245e', paddingVertical: 4, paddingHorizontal: 8, borderRadius: 12, }, textoBadgeDesconto: { color: '#fff', fontSize: 10, fontWeight: 'bold', }, imagemProduto: { width: '100%', height: 140, backgroundColor: '#f8f9fa', }, infoContainer: { padding: 10, flex: 1, justifyContent: 'space-between', }, nomeProduto: { fontSize: 14, fontWeight: 'bold', color: '#14171a', marginBottom: 4, lineHeight: 18, }, descricaoProduto: { fontSize: 12, color: '#657786', marginBottom: 8, }, precosContainer: { marginBottom: 10, }, precoNormal: { fontSize: 12, color: '#657786', textDecorationLine: 'line-through', marginBottom: 2, }, precoVenda: { fontSize: 16, fontWeight: 'bold', color: '#1a1a1a', }, valorDescontoDescricao: { fontSize: 10, color: '#28a745', fontWeight: '600', marginTop: 2, }, botaoEditar: { backgroundColor: '#FF9F43', paddingVertical: 8, borderRadius: 6, alignItems: 'center', marginTop: 4, }, textoBotaoEditar: { color: '#fff', fontSize: 13, fontWeight: 'bold', }, });

Compartilhe:

Profissional engajado com as últimas tendências tecnológicas e de gestão, buscando continuamente aprimorar suas competências e compartilhar seu conhecimento.

1 comentário

Publicar comentário