chore: sincroniza projeto para gitea
This commit is contained in:
671
client/src/pages/SiteCatalogo.js
Normal file
671
client/src/pages/SiteCatalogo.js
Normal file
@@ -0,0 +1,671 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
FiGlobe,
|
||||
FiPackage,
|
||||
FiImage,
|
||||
FiEye,
|
||||
FiEyeOff,
|
||||
FiRefreshCw,
|
||||
FiSave,
|
||||
FiSettings
|
||||
} from 'react-icons/fi';
|
||||
import toast from 'react-hot-toast';
|
||||
import '../styles/site-catalogo.css';
|
||||
import '../styles/site-catalogo-table.css';
|
||||
|
||||
const SiteCatalogo = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [produtos, setProdutos] = useState([]);
|
||||
const [precosPromocionais, setPrecosPromocionais] = useState({});
|
||||
const [precosOriginais, setPrecosOriginais] = useState({});
|
||||
const [precosAlterados, setPrecosAlterados] = useState({});
|
||||
const [precosSalvando, setPrecosSalvando] = useState({});
|
||||
const [catalogoConfig, setCatalogoConfig] = useState({
|
||||
catalogoAtivo: false,
|
||||
exibirPrecos: true,
|
||||
exibirEstoque: false,
|
||||
exibirNovidades: true,
|
||||
exibirPromocoes: true
|
||||
});
|
||||
const [modalFotosOpen, setModalFotosOpen] = useState(false);
|
||||
const [produtoSelecionado, setProdutoSelecionado] = useState(null);
|
||||
const [fotosAdicionais, setFotosAdicionais] = useState([]);
|
||||
const [uploadingPhoto, setUploadingPhoto] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
carregarProdutos();
|
||||
carregarConfiguracoes();
|
||||
}, []);
|
||||
|
||||
const carregarProdutos = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch('/api/produtos');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setProdutos(data);
|
||||
const precos = {};
|
||||
const originais = {};
|
||||
data.forEach((produto) => {
|
||||
const valor = produto.preco_promocional;
|
||||
const valorFormatado =
|
||||
valor === null || valor === undefined || valor === ''
|
||||
? ''
|
||||
: Number(valor).toFixed(2);
|
||||
precos[produto.id] = valorFormatado;
|
||||
originais[produto.id] = valorFormatado;
|
||||
});
|
||||
setPrecosPromocionais(precos);
|
||||
setPrecosOriginais(originais);
|
||||
setPrecosAlterados({});
|
||||
setPrecosSalvando({});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar produtos:', error);
|
||||
toast.error('Erro ao carregar produtos');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const carregarConfiguracoes = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/configuracoes/catalogo');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setCatalogoConfig(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar configurações:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleProdutoPromocao = async (produtoId, emPromocao) => {
|
||||
try {
|
||||
const response = await fetch(`/api/produtos/${produtoId}/promocao`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ emPromocao: !emPromocao }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
toast.success('Promoção atualizada!');
|
||||
carregarProdutos();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar promoção:', error);
|
||||
toast.error('Erro ao atualizar promoção');
|
||||
}
|
||||
};
|
||||
|
||||
const toggleProdutoNovidade = async (produtoId, novidade) => {
|
||||
try {
|
||||
const response = await fetch(`/api/produtos/${produtoId}/novidade`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ novidade: !novidade }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
toast.success('Novidade atualizada!');
|
||||
carregarProdutos();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar novidade:', error);
|
||||
toast.error('Erro ao atualizar novidade');
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrecoPromocionalChange = (produtoId, valor) => {
|
||||
setPrecosPromocionais(prev => ({
|
||||
...prev,
|
||||
[produtoId]: valor
|
||||
}));
|
||||
setPrecosAlterados(prev => ({
|
||||
...prev,
|
||||
[produtoId]: valor !== (precosOriginais[produtoId] ?? '')
|
||||
}));
|
||||
};
|
||||
|
||||
const handlePrecoPromocionalBlur = (produtoId) => {
|
||||
const valorAtual = precosPromocionais[produtoId];
|
||||
|
||||
if (valorAtual === '' || valorAtual === null || valorAtual === undefined) {
|
||||
setPrecosAlterados(prev => ({
|
||||
...prev,
|
||||
[produtoId]: (precosOriginais[produtoId] ?? '') !== ''
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const numero = Number(valorAtual);
|
||||
if (Number.isNaN(numero)) {
|
||||
toast.error('Informe um valor numérico válido');
|
||||
return;
|
||||
}
|
||||
|
||||
const formatado = numero.toFixed(2);
|
||||
|
||||
setPrecosPromocionais(prev => ({
|
||||
...prev,
|
||||
[produtoId]: formatado
|
||||
}));
|
||||
|
||||
setPrecosAlterados(prev => ({
|
||||
...prev,
|
||||
[produtoId]: formatado !== (precosOriginais[produtoId] ?? '')
|
||||
}));
|
||||
};
|
||||
|
||||
const salvarPrecoPromocional = async (produtoId) => {
|
||||
const valorAtual = precosPromocionais[produtoId];
|
||||
const precoFormatado = valorAtual === '' || valorAtual === null || valorAtual === undefined
|
||||
? ''
|
||||
: Number(valorAtual).toFixed(2);
|
||||
const precoPayload = valorAtual === '' || valorAtual === null || valorAtual === undefined
|
||||
? null
|
||||
: Number(valorAtual);
|
||||
|
||||
if (precoPayload !== null && (Number.isNaN(precoPayload) || precoPayload < 0)) {
|
||||
toast.error('Informe um valor válido para o desconto');
|
||||
return;
|
||||
}
|
||||
|
||||
setPrecosSalvando(prev => ({
|
||||
...prev,
|
||||
[produtoId]: true
|
||||
}));
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/produtos/${produtoId}/preco-promocional`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ precoPromocional: precoPayload }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
toast.success('Preço promocional atualizado!');
|
||||
setPrecosOriginais(prev => ({
|
||||
...prev,
|
||||
[produtoId]: precoFormatado
|
||||
}));
|
||||
setPrecosPromocionais(prev => ({
|
||||
...prev,
|
||||
[produtoId]: precoFormatado
|
||||
}));
|
||||
setPrecosAlterados(prev => ({
|
||||
...prev,
|
||||
[produtoId]: false
|
||||
}));
|
||||
setProdutos(prev =>
|
||||
prev.map(produto =>
|
||||
produto.id === produtoId
|
||||
? { ...produto, preco_promocional: precoPayload }
|
||||
: produto
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
throw new Error(data.message || 'Erro ao atualizar preço');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar preço:', error);
|
||||
toast.error('Erro ao atualizar preço');
|
||||
} finally {
|
||||
setPrecosSalvando(prev => ({
|
||||
...prev,
|
||||
[produtoId]: false
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const salvarConfiguracoes = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch('/api/configuracoes/catalogo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(catalogoConfig),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
toast.success('Configurações salvas com sucesso!');
|
||||
} else {
|
||||
throw new Error('Erro ao salvar configurações');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao salvar configurações:', error);
|
||||
toast.error('Erro ao salvar configurações');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleProdutoVisivel = async (produtoId, visivelAtual) => {
|
||||
try {
|
||||
const response = await fetch(`/api/produtos/${produtoId}/visibilidade`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ visivelCatalogo: !visivelAtual }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
toast.success('Visibilidade atualizada!');
|
||||
carregarProdutos();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar visibilidade:', error);
|
||||
toast.error('Erro ao atualizar visibilidade');
|
||||
}
|
||||
};
|
||||
|
||||
const abrirGerenciarFotos = async (produto) => {
|
||||
setProdutoSelecionado(produto);
|
||||
setModalFotosOpen(true);
|
||||
await carregarFotosAdicionais(produto.id);
|
||||
};
|
||||
|
||||
const carregarFotosAdicionais = async (produtoId) => {
|
||||
try {
|
||||
const response = await fetch(`/api/produtos/${produtoId}/fotos-catalogo`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setFotosAdicionais(data.fotos || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar fotos adicionais:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadFoto = async (event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file || !produtoSelecionado) return;
|
||||
|
||||
// Validar tipo de arquivo
|
||||
const tiposPermitidos = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'];
|
||||
if (!tiposPermitidos.includes(file.type)) {
|
||||
toast.error('Tipo de arquivo não permitido. Use JPEG, PNG, WebP ou GIF');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validar tamanho (5MB)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
toast.error('Arquivo muito grande. Máximo 5MB');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('foto', file);
|
||||
|
||||
try {
|
||||
setUploadingPhoto(true);
|
||||
const response = await fetch(`/api/produtos/${produtoSelecionado.id}/fotos-catalogo`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
toast.success('Foto adicionada com sucesso!');
|
||||
await carregarFotosAdicionais(produtoSelecionado.id);
|
||||
await carregarProdutos();
|
||||
// Limpar input
|
||||
event.target.value = '';
|
||||
} else {
|
||||
const errorMsg = data.error || 'Erro ao fazer upload';
|
||||
console.error('Erro do servidor:', data);
|
||||
toast.error(errorMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao fazer upload:', error);
|
||||
toast.error('Erro ao adicionar foto: ' + error.message);
|
||||
} finally {
|
||||
setUploadingPhoto(false);
|
||||
}
|
||||
};
|
||||
|
||||
const deletarFoto = async (fileName) => {
|
||||
if (!produtoSelecionado) return;
|
||||
|
||||
if (!window.confirm('Tem certeza que deseja remover esta foto?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/produtos/${produtoSelecionado.id}/fotos-catalogo/${fileName}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
toast.success('Foto removida!');
|
||||
await carregarFotosAdicionais(produtoSelecionado.id);
|
||||
await carregarProdutos();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao deletar foto:', error);
|
||||
toast.error('Erro ao remover foto');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading && produtos.length === 0) {
|
||||
return (
|
||||
<div className="loading">
|
||||
<div>Carregando catálogo...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const produtosVisiveis = produtos.filter(p => p.visivel_catalogo);
|
||||
const produtosPromocao = produtos.filter(p => p.em_promocao && p.visivel_catalogo);
|
||||
const produtosNovidade = produtos.filter(p => p.novidade && p.visivel_catalogo);
|
||||
|
||||
return (
|
||||
<div className="site-catalogo fade-in">
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1>
|
||||
<FiGlobe style={{ marginRight: '10px' }} />
|
||||
Site / Catálogo
|
||||
</h1>
|
||||
<p>Gerencie os produtos visíveis no catálogo online</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={carregarProdutos}
|
||||
>
|
||||
<FiRefreshCw />
|
||||
Atualizar
|
||||
</button>
|
||||
<a
|
||||
href="/catalogo"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn btn-primary"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<FiGlobe />
|
||||
Ver Catálogo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Configurações do Catálogo */}
|
||||
<div className="config-section">
|
||||
<div className="config-header">
|
||||
<div className="config-title">
|
||||
<FiSettings className="config-icon" />
|
||||
<div>
|
||||
<h2>Configurações do Catálogo</h2>
|
||||
<p>Configure as opções de exibição do catálogo online</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="config-form">
|
||||
<p className="config-info-text">
|
||||
As opções de exibição do catálogo são gerenciadas automaticamente. Utilize o botão abaixo caso precise sincronizar as configurações.
|
||||
</p>
|
||||
|
||||
<div className="config-actions">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-primary ${loading ? 'loading' : ''}`}
|
||||
onClick={salvarConfiguracoes}
|
||||
disabled={loading}
|
||||
>
|
||||
<FiSettings />
|
||||
{loading ? 'Salvando...' : 'Salvar Configurações'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Estatísticas */}
|
||||
<div className="catalogo-stats">
|
||||
<div className="stat-card">
|
||||
<FiPackage className="stat-icon" />
|
||||
<div className="stat-info">
|
||||
<span className="stat-value">{produtos.length}</span>
|
||||
<span className="stat-label">Total de Produtos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="stat-card success">
|
||||
<FiEye className="stat-icon" />
|
||||
<div className="stat-info">
|
||||
<span className="stat-value">{produtosVisiveis.length}</span>
|
||||
<span className="stat-label">Visíveis</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="stat-card warning">
|
||||
<FiEyeOff className="stat-icon" />
|
||||
<div className="stat-info">
|
||||
<span className="stat-value">{produtos.length - produtosVisiveis.length}</span>
|
||||
<span className="stat-label">Ocultos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="stat-card" style={{ background: 'linear-gradient(135deg, #ffd89b 0%, #f59e0b 100%)', color: 'white' }}>
|
||||
<span className="stat-icon" style={{ fontSize: '2rem' }}>🏷️</span>
|
||||
<div className="stat-info">
|
||||
<span className="stat-value">{produtosPromocao.length}</span>
|
||||
<span className="stat-label">Em Promoção</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="stat-card" style={{ background: 'linear-gradient(135deg, #a8edea 0%, #3b82f6 100%)', color: 'white' }}>
|
||||
<span className="stat-icon" style={{ fontSize: '2rem' }}>✨</span>
|
||||
<div className="stat-info">
|
||||
<span className="stat-value">{produtosNovidade.length}</span>
|
||||
<span className="stat-label">Novidades</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lista de Produtos */}
|
||||
<div className="products-section">
|
||||
<h2>
|
||||
<FiPackage style={{ marginRight: '10px' }} />
|
||||
Produtos do Catálogo
|
||||
</h2>
|
||||
|
||||
{produtos.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<FiPackage size={48} />
|
||||
<p>Nenhum produto cadastrado</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="products-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Foto</th>
|
||||
<th>Produto</th>
|
||||
<th>Preço Normal</th>
|
||||
<th>Preço Promocional</th>
|
||||
<th>Estoque</th>
|
||||
<th>Status</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{produtos.map((produto) => (
|
||||
<tr
|
||||
key={produto.id}
|
||||
className={!produto.visivel_catalogo ? 'row-hidden' : ''}
|
||||
>
|
||||
<td>
|
||||
<div className="table-product-image">
|
||||
{produto.foto_principal_url || produto.imagem ? (
|
||||
<img src={produto.foto_principal_url || produto.imagem} alt={produto.nome} />
|
||||
) : (
|
||||
<div className="no-image">
|
||||
<FiImage size={20} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="table-product-info">
|
||||
<strong>{produto.nome}</strong>
|
||||
<small>{produto.marca || 'Sem marca'}</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className="price-normal">
|
||||
R$ {parseFloat(produto.valor_revenda || 0).toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="promo-input-wrapper">
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
className={`input-preco-promo ${precosAlterados[produto.id] ? 'changed' : ''}`}
|
||||
placeholder="0.00"
|
||||
value={precosPromocionais[produto.id] ?? ''}
|
||||
onChange={(e) => handlePrecoPromocionalChange(produto.id, e.target.value)}
|
||||
onBlur={() => handlePrecoPromocionalBlur(produto.id)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn-icon promo-save-btn ${precosAlterados[produto.id] ? 'active' : ''}`}
|
||||
title={precosAlterados[produto.id] ? 'Salvar preço promocional' : 'Nenhuma alteração pendente'}
|
||||
onClick={() => salvarPrecoPromocional(produto.id)}
|
||||
disabled={!precosAlterados[produto.id] || precosSalvando[produto.id]}
|
||||
>
|
||||
{precosSalvando[produto.id] ? (
|
||||
<FiRefreshCw className="icon-spin" />
|
||||
) : (
|
||||
<FiSave />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`stock-badge ${produto.estoque_total > 0 ? 'in-stock' : 'out-stock'}`}>
|
||||
{produto.estoque_total || 0}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="status-badges">
|
||||
<span
|
||||
className={`badge ${produto.visivel_catalogo ? 'badge-success' : 'badge-secondary'}`}
|
||||
title={produto.visivel_catalogo ? 'Visível' : 'Oculto'}
|
||||
>
|
||||
{produto.visivel_catalogo ? <FiEye /> : <FiEyeOff />}
|
||||
</span>
|
||||
<span
|
||||
className={`badge ${produto.novidade ? 'badge-info' : 'badge-light'}`}
|
||||
title="Novidade"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => toggleProdutoNovidade(produto.id, produto.novidade)}
|
||||
>
|
||||
✨ {produto.novidade ? 'NOVO' : ''}
|
||||
</span>
|
||||
<span
|
||||
className={`badge ${produto.em_promocao ? 'badge-warning' : 'badge-light'}`}
|
||||
title="Promoção"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => toggleProdutoPromocao(produto.id, produto.em_promocao)}
|
||||
>
|
||||
🏷️ {produto.em_promocao ? 'PROMO' : ''}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="table-actions">
|
||||
<button
|
||||
className="btn-icon"
|
||||
onClick={() => toggleProdutoVisivel(produto.id, produto.visivel_catalogo)}
|
||||
title={produto.visivel_catalogo ? 'Ocultar' : 'Mostrar'}
|
||||
>
|
||||
{produto.visivel_catalogo ? <FiEyeOff /> : <FiEye />}
|
||||
</button>
|
||||
<button
|
||||
className="btn-icon"
|
||||
onClick={() => abrirGerenciarFotos(produto)}
|
||||
title="Gerenciar fotos"
|
||||
>
|
||||
<FiImage />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal de Gerenciamento de Fotos */}
|
||||
{modalFotosOpen && produtoSelecionado && (
|
||||
<div className="modal-overlay" onClick={() => setModalFotosOpen(false)}>
|
||||
<div className="modal-content-fotos" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-header">
|
||||
<h2>
|
||||
<FiImage style={{ marginRight: '10px' }} />
|
||||
Gerenciar Fotos - {produtoSelecionado.nome}
|
||||
</h2>
|
||||
<button className="modal-close" onClick={() => setModalFotosOpen(false)}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
<div className="upload-section">
|
||||
<label className="upload-button">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleUploadFoto}
|
||||
disabled={uploadingPhoto}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
<FiImage />
|
||||
{uploadingPhoto ? 'Enviando...' : 'Adicionar Nova Foto'}
|
||||
</label>
|
||||
<p className="upload-help">
|
||||
Adicione fotos extras que aparecerão no catálogo online
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="fotos-grid">
|
||||
{fotosAdicionais.length === 0 ? (
|
||||
<div className="empty-fotos">
|
||||
<FiImage size={48} />
|
||||
<p>Nenhuma foto adicional</p>
|
||||
<span>Adicione fotos para exibir no catálogo</span>
|
||||
</div>
|
||||
) : (
|
||||
fotosAdicionais.map((foto) => (
|
||||
<div key={foto.name} className="foto-item">
|
||||
<img src={foto.url} alt={foto.name} />
|
||||
<button
|
||||
className="btn-delete-foto"
|
||||
onClick={() => deletarFoto(foto.name)}
|
||||
title="Remover foto"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SiteCatalogo;
|
||||
Reference in New Issue
Block a user