arbritage/pages/Settings.tsx

453 lines
32 KiB
TypeScript
Raw Normal View History

2026-01-26 14:20:25 +00:00
import React, { useState } from 'react';
import { Settings as SettingsIcon, Truck, Key, Building, Users, Save, Globe, Smartphone, Bell, Shield, FileCheck, ShoppingBag, RefreshCw, AlertCircle, Upload, Palette } from 'lucide-react';
import clsx from 'clsx';
import { useCRM } from '../context/CRMContext';
import { useTheme } from '../context/ThemeContext';
const ThemeSelector = () => {
const { theme, setTheme } = useTheme();
return (
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Tema do Sistema</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[
{ id: 'light', name: 'Simples (Clean)', color: 'bg-slate-50 border-2 border-slate-200', accent: 'bg-blue-600', text: 'Visual limpo e direto.' },
{ id: 'ocean', name: 'Complexo (Ocean)', color: 'bg-[#0f172a]', accent: 'bg-cyan-500', text: 'Alta densidade e contraste.' },
{ id: 'dark', name: 'Completo (Pro)', color: 'bg-[#0a0a0c]', accent: 'bg-indigo-600', text: 'Interface imersiva padrão.' },
].map(t => (
<button
key={t.id}
onClick={() => setTheme(t.id as any)}
className={clsx(
"group relative rounded-2xl overflow-hidden transition-all duration-300 border-2 flex flex-col items-start text-left",
theme === t.id ? "border-indigo-500 ring-2 ring-indigo-500/20 scale-[1.02]" : "border-white/5 hover:border-white/10"
)}
>
{/* Preview Header */}
<div className={clsx("h-24 w-full relative", t.color)}>
<div className="absolute top-3 left-3 flex gap-1.5">
<div className="w-2 h-2 rounded-full bg-red-400/50"></div>
<div className="w-2 h-2 rounded-full bg-amber-400/50"></div>
<div className="w-2 h-2 rounded-full bg-emerald-400/50"></div>
</div>
<div className={clsx("absolute bottom-3 right-3 px-2 py-1 rounded text-[10px] font-bold text-white shadow-lg", t.accent)}>
Aa
</div>
{/* Fake UI Lines */}
<div className="absolute top-8 left-3 w-3/4 h-2 bg-white/5 rounded-full"></div>
<div className="absolute top-12 left-3 w-1/2 h-2 bg-white/5 rounded-full"></div>
</div>
{/* Content */}
<div className="p-5 bg-card w-full">
<div className="flex justify-between items-center mb-1">
<span className="text-sm font-bold text-foreground">{t.name}</span>
{theme === t.id && <div className="w-2 h-2 rounded-full bg-emerald-500 shadow-emerald-500/50 shadow-lg"></div>}
</div>
<p className="text-xs text-muted-foreground">{t.text}</p>
</div>
</button>
))}
</div>
</div>
);
};
const Settings: React.FC = () => {
const { overheadPercent, exchangeRate, settings, updateSettings } = useCRM();
const [activeTab, setActiveTab] = useState<'general' | 'fiscal' | 'marketplaces' | 'logistics' | 'email' | 'notifications'>('general');
// Local state for form, initialized with context settings
const [config, setConfig] = useState(settings || {
companyName: 'Arbitra System',
cnpj: '',
ie: '',
defaultOverhead: 20,
defaultExchange: 5.65,
certificateName: '',
certificatePassword: '',
geminiKey: 'sk-....................',
melhorEnvioToken: '',
blingToken: '',
tinyToken: '',
whatsappNumber: '',
nfeSerie: '1',
nfeNumber: '159',
autoSyncSales: true,
autoSyncStock: true,
smtpHost: '',
smtpPort: '587',
smtpUser: '',
smtpPass: '',
});
// Sync state when settings load from DB
React.useEffect(() => {
if (settings) {
setConfig(prev => ({ ...prev, ...settings }));
}
}, [settings]);
const handleSave = async () => {
try {
await updateSettings(config);
alert('Configurações salvas com sucesso!');
} catch (error) {
console.error(error);
alert('Erro ao salvar configurações.');
}
};
const tabs = [
{ id: 'general', label: 'Geral', icon: Building, description: 'Dados cadastrais' },
{ id: 'fiscal', label: 'Fiscal & NFe', icon: FileCheck, description: 'Certificado e tributação' },
{ id: 'appearance', label: 'Aparência', icon: Palette, description: 'Temas e Cores' },
{ id: 'marketplaces', label: 'Marketplaces', icon: ShoppingBag, description: 'Integrações de vendas' },
{ id: 'logistics', label: 'Logística', icon: Truck, description: 'Fretes e entregas' },
{ id: 'email', label: 'Email SMTP', icon: Globe, description: 'Configuração de envio' },
{ id: 'notifications', label: 'Notificações', icon: Bell, description: 'Alertas e avisos' },
];
return (
<div className="animate-in fade-in duration-500 container mx-auto max-w-[1600px] pb-20">
<div className="flex flex-col md:flex-row justify-between items-end mb-6 border-b border-white/5 pb-6">
<div>
<h1 className="text-2xl font-bold tracking-tight text-white mb-1">Configurações do Sistema</h1>
<p className="text-sm text-slate-400">Gerencie todos os parâmetros fiscais, integrações e regras de negócio.</p>
</div>
<button
onClick={handleSave}
className="bg-indigo-600 hover:bg-indigo-500 text-white px-6 py-2.5 rounded-lg font-bold text-sm shadow-lg shadow-indigo-900/20 active:scale-95 transition-all flex items-center gap-2"
>
<Save size={16} /> Salvar Alterações
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* Denser Sidebar */}
<div className="lg:col-span-2 flex flex-col gap-1">
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as any)}
className={clsx(
"text-left px-4 py-3 rounded-lg transition-all border group flex items-center gap-3",
activeTab === tab.id
? "bg-indigo-600/10 border-indigo-500/30 text-white"
: "bg-transparent border-transparent text-slate-400 hover:bg-white/5 hover:text-slate-200"
)}
>
<tab.icon size={16} className={activeTab === tab.id ? "text-indigo-400" : "text-slate-500"} />
<div className="leading-tight">
<p className="font-semibold text-xs uppercase tracking-wide">{tab.label}</p>
</div>
</button>
))}
</div>
{/* Content Area - Denser Forms */}
<div className="lg:col-span-10">
<div className="bg-[#0F1115] border border-white/10 rounded-xl p-6 shadow-sm min-h-[600px]">
{activeTab === 'general' && (
<div className="space-y-8 animate-in fade-in duration-300">
{/* Section */}
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Identificação da Empresa</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="md:col-span-1 space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">CNPJ</label>
<input
value={config.cnpj}
onChange={e => setConfig({ ...config, cnpj: e.target.value })}
placeholder="00.000.000/0000-00"
className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all font-mono"
/>
</div>
<div className="md:col-span-1 space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Inscrição Estadual</label>
<input
value={config.ie}
onChange={e => setConfig({ ...config, ie: e.target.value })}
className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all"
/>
</div>
<div className="md:col-span-1 space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Nome Fantasia</label>
<input
value={config.companyName}
onChange={e => setConfig({ ...config, companyName: e.target.value })}
className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all"
/>
</div>
</div>
</div>
{/* Section */}
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Parâmetros Financeiros</h3>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Cotação Dólar (Base)</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 text-xs font-bold">R$</span>
<input
type="number"
value={config.defaultExchange}
onChange={e => setConfig({ ...config, defaultExchange: parseFloat(e.target.value) })}
className="w-full bg-black/20 border border-white/10 rounded-lg pl-8 pr-3 py-2 text-sm text-slate-200 focus:border-emerald-500 outline-none transition-all font-mono"
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Overhead Padrão (%)</label>
<div className="relative">
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 text-xs font-bold">%</span>
<input
type="number"
value={config.defaultOverhead}
onChange={e => setConfig({ ...config, defaultOverhead: parseFloat(e.target.value) })}
className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all font-mono"
/>
</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'appearance' && (
<div className="space-y-8 animate-in fade-in duration-300">
<ThemeSelector />
</div>
)}
{activeTab === 'fiscal' && (
<div className="space-y-8 animate-in fade-in duration-300">
{/* Certificate */}
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Certificado Digital (A1)</h3>
<div className="bg-white/5 border border-white/5 rounded-lg p-6 flex flex-col items-center justify-center border-dashed group hover:border-indigo-500/50 transition-colors cursor-pointer">
<Upload className="text-slate-500 mb-2 group-hover:text-indigo-400" size={32} />
<p className="text-sm font-bold text-slate-300 group-hover:text-white">Clique para selecionar o Certificado .PFX</p>
<p className="text-xs text-slate-500 mt-1">Sua senha será solicitada após o upload</p>
</div>
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Senha do Certificado</label>
<input
type="password"
value={config.certificatePassword}
onChange={e => setConfig({ ...config, certificatePassword: e.target.value })}
className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Validade</label>
<div className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-500 cursor-not-allowed flex items-center justify-between">
<span>--/--/----</span>
<AlertCircle size={14} className="text-amber-500" />
</div>
</div>
</div>
</div>
{/* NFe Config */}
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Configuração de Emissão (NFe 4.0)</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Próximo Número NFe</label>
<input
type="number"
value={config.nfeNumber}
onChange={e => setConfig({ ...config, nfeNumber: e.target.value })}
className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all font-mono"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Série</label>
<input
type="number"
value={config.nfeSerie}
onChange={e => setConfig({ ...config, nfeSerie: e.target.value })}
className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all font-mono"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-500 uppercase">Ambiente</label>
<select className="w-full bg-black/20 border border-white/10 rounded-lg px-3 py-2 text-sm text-slate-200 focus:border-indigo-500 outline-none transition-all">
<option value="homologacao">Homologação (Teste)</option>
<option value="producao">Produção</option>
</select>
</div>
</div>
</div>
</div>
)}
{activeTab === 'marketplaces' && (
<div className="space-y-8 animate-in fade-in duration-300">
{/* Major Marketplaces */}
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Hub de Integração (Mais de 30 canais)</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[
{ name: 'Mercado Livre', color: 'bg-yellow-500', status: 'connected', sales: 1240 },
{ name: 'Shopee', color: 'bg-orange-600', status: 'error', sales: 0 },
{ name: 'Amazon Seller', color: 'bg-slate-800', status: 'disconnected', sales: 0 },
{ name: 'Magalu', color: 'bg-blue-600', status: 'disconnected', sales: 0 },
{ name: 'Via Varejo', color: 'bg-green-600', status: 'disconnected', sales: 0 },
{ name: 'B2W (Americanas)', color: 'bg-red-600', status: 'disconnected', sales: 0 },
{ name: 'Shein', color: 'bg-black', status: 'disconnected', sales: 0 },
{ name: 'AliExpress', color: 'bg-red-500', status: 'disconnected', sales: 0 },
].map(mk => (
<div key={mk.name} className="flex flex-col justify-between p-4 bg-white/5 border border-white/5 rounded-lg hover:bg-white/10 transition-colors h-[140px]">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className={clsx("w-10 h-10 rounded-lg flex items-center justify-center text-white font-bold text-xs shadow-lg", mk.color)}>
{mk.name.substring(0, 2)}
</div>
<div>
<p className="text-sm font-bold text-white leading-tight">{mk.name}</p>
{mk.status === 'connected' && <p className="text-[10px] text-emerald-400"> Online</p>}
{mk.status === 'error' && <p className="text-[10px] text-rose-400"> Erro</p>}
{mk.status === 'disconnected' && <p className="text-[10px] text-slate-500"> Offline</p>}
</div>
</div>
{/* Toggle Switch Mockup */}
<div className={clsx("w-8 h-4 rounded-full relative transition-colors", mk.status === 'connected' ? "bg-emerald-500/20" : "bg-white/10")}>
<div className={clsx("absolute top-0.5 w-3 h-3 rounded-full transition-all", mk.status === 'connected' ? "left-4 bg-emerald-500" : "left-0.5 bg-slate-500")}></div>
</div>
</div>
<div className="mt-4 pt-3 border-t border-white/5 flex items-center justify-between">
<div>
{mk.status === 'connected' ? (
<>
<p className="text-[10px] text-slate-500 uppercase font-bold">Vendas Hoje</p>
<p className="text-sm font-bold text-white">R$ {mk.sales.toLocaleString('pt-BR')}</p>
</>
) : (
<p className="text-[10px] text-slate-600 italic">Clique para configurar</p>
)}
</div>
<button className="px-3 py-1.5 rounded bg-white/5 text-[10px] font-bold text-slate-300 hover:bg-white/20 transition-colors uppercase tracking-wide">
{mk.status === 'disconnected' ? 'Configurar' : 'Gerenciar'}
</button>
</div>
</div>
))}
</div>
</div>
{/* Custom Sites / E-commerce Platforms */}
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Lojas Virtuais & Sites Próprios</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[
{ name: 'WooCommerce', color: 'bg-purple-600' },
{ name: 'Shopify', color: 'bg-emerald-500' },
{ name: 'Nuvemshop', color: 'bg-blue-500' },
{ name: 'Vtex', color: 'bg-pink-600' },
{ name: 'API Personalizada', color: 'bg-slate-700', isCustom: true },
].map(platform => (
<div key={platform.name} className="flex items-center justify-between p-3 bg-white/5 border border-white/5 rounded-lg hover:border-indigo-500/30 transition-all cursor-pointer group">
<div className="flex items-center gap-3">
<div className={clsx("w-8 h-8 rounded-lg flex items-center justify-center text-white font-bold text-[10px]", platform.color)}>
{platform.isCustom ? <Key size={14} /> : platform.name.substring(0, 1)}
</div>
<span className="text-sm font-medium text-slate-300 group-hover:text-white transition-colors">{platform.name}</span>
</div>
<div className="w-6 h-6 rounded-full border border-white/10 flex items-center justify-center group-hover:bg-indigo-500 group-hover:border-indigo-500 transition-all">
<div className="w-1.5 h-1.5 bg-white rounded-full opacity-0 group-hover:opacity-100"></div>
</div>
</div>
))}
</div>
<p className="text-[10px] text-slate-500 mt-2">* Para API Personalizada, consulte a documentação /docs/api-v1</p>
</div>
{/* Automation Settings */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Regras de Sincronização</h3>
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-black/20 rounded-lg border border-white/5">
<div>
<p className="text-xs font-bold text-slate-200">Importar Pedidos</p>
<p className="text-[10px] text-slate-500">Baixar vendas novas a cada 5 min.</p>
</div>
<input type="checkbox" checked={config.autoSyncSales} onChange={() => setConfig({ ...config, autoSyncSales: !config.autoSyncSales })} className="accent-emerald-500 w-4 h-4" />
</div>
<div className="flex items-center justify-between p-3 bg-black/20 rounded-lg border border-white/5">
<div>
<p className="text-xs font-bold text-slate-200">Atualizar Estoque</p>
<p className="text-[10px] text-slate-500">Enviar saldo local para os canais.</p>
</div>
<input type="checkbox" checked={config.autoSyncStock} onChange={() => setConfig({ ...config, autoSyncStock: !config.autoSyncStock })} className="accent-emerald-500 w-4 h-4" />
</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'logistics' && (
<div className="space-y-8 animate-in fade-in duration-300">
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wider mb-4 border-b border-white/5 pb-2">Hubs de Frete</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 rounded-lg bg-white/5 border border-white/5 space-y-3">
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<Truck size={18} className="text-blue-400" />
<span className="font-bold text-sm text-slate-200">Melhor Envio</span>
</div>
<span className="text-[10px] font-bold text-emerald-400 bg-emerald-400/10 px-2 py-0.5 rounded">Ativo</span>
</div>
<input
value={config.melhorEnvioToken}
onChange={e => setConfig({ ...config, melhorEnvioToken: e.target.value })}
placeholder="Token de Produção"
type="password"
className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-slate-400 text-xs focus:border-indigo-500 outline-none transition-all font-mono"
/>
</div>
<div className="p-4 rounded-lg bg-white/5 border border-white/5 space-y-3 opacity-50">
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<Truck size={18} className="text-orange-400" />
<span className="font-bold text-sm text-slate-200">Frenet</span>
</div>
<span className="text-[10px] font-bold text-slate-500 bg-white/5 px-2 py-0.5 rounded">Inativo</span>
</div>
<input
placeholder="Token de Acesso"
disabled
className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-slate-400 text-xs focus:border-indigo-500 outline-none transition-all font-mono"
/>
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default Settings;