Files
hefesto/frontend/src/pages/Configuracao.tsx

433 lines
18 KiB
TypeScript

import { useState, useEffect } from 'react'
import { Settings, Plus, Edit2, Trash2, Loader2, X, Save } from 'lucide-react'
import api from '../services/api'
import Modal from '../components/Modal'
type Tab = 'categorias' | 'centros_custo' | 'subcategorias' | 'locais'
const tabs: { key: Tab; label: string }[] = [
{ key: 'categorias', label: 'Categorias' },
{ key: 'centros_custo', label: 'Centros de Custo' },
{ key: 'subcategorias', label: 'Subcategorias' },
{ key: 'locais', label: 'Locais' },
]
export default function Configuracao() {
const [activeTab, setActiveTab] = useState<Tab>('categorias')
const [loading, setLoading] = useState(true)
const [items, setItems] = useState<any[]>([])
const [showModal, setShowModal] = useState(false)
const [editId, setEditId] = useState<string | null>(null)
const [form, setForm] = useState<any>({})
const [saving, setSaving] = useState(false)
// For subcategorias
const [categorias, setCategorias] = useState<any[]>([])
const endpoints: Record<Tab, string> = {
categorias: '/categorias',
centros_custo: '/centros-custo',
subcategorias: '/subcategorias',
locais: '/locais',
}
useEffect(() => { fetchItems(); fetchCategorias() }, [activeTab])
const fetchItems = async () => {
setLoading(true)
try {
const { data } = await api.get(endpoints[activeTab])
setItems(data)
} catch { setItems([]) }
finally { setLoading(false) }
}
const fetchCategorias = async () => {
try {
const { data } = await api.get('/categorias')
setCategorias(data)
} catch {}
}
const getEmptyForm = (): any => {
switch (activeTab) {
case 'categorias': return { nome: '', criticidade_padrao: 'media', sla_dias: 30, tipo_investimento: '', tipo_manutencao: '', impacto_ambiental_esperado: '', potencial_geracao_residuos: '' }
case 'centros_custo': return { codigo: '', nome: '' }
case 'subcategorias': return { nome: '', categoria_id: '' }
case 'locais': return { nome: '', endereco: '', tipo_operacao_local: '', classificacao_impacto_ambiental: '', praticas_sustentaveis: [] }
}
}
const openNew = () => { setEditId(null); setForm(getEmptyForm()); setShowModal(true) }
const openEdit = (item: any) => {
setEditId(item.id)
switch (activeTab) {
case 'categorias':
setForm({ nome: item.nome, criticidade_padrao: item.criticidade_padrao || 'media', sla_dias: item.sla_dias || 30, tipo_investimento: item.tipo_investimento || '', tipo_manutencao: item.tipo_manutencao || '', impacto_ambiental_esperado: item.impacto_ambiental_esperado || '', potencial_geracao_residuos: item.potencial_geracao_residuos || '' })
break
case 'centros_custo':
setForm({ codigo: item.codigo || '', nome: item.nome })
break
case 'subcategorias':
setForm({ nome: item.nome, categoria_id: item.categoria_id || '' })
break
case 'locais':
setForm({ nome: item.nome, endereco: item.endereco || '', tipo_operacao_local: item.tipo_operacao_local || '', classificacao_impacto_ambiental: item.classificacao_impacto_ambiental || '', praticas_sustentaveis: item.praticas_sustentaveis || [] })
break
}
setShowModal(true)
}
const handleSubmit = async () => {
setSaving(true)
try {
if (editId) {
await api.patch(`${endpoints[activeTab]}/${editId}`, form)
} else {
await api.post(endpoints[activeTab], form)
}
setShowModal(false)
fetchItems()
} catch (err) {
alert('Erro ao salvar')
} finally {
setSaving(false)
}
}
const handleDelete = async (id: string) => {
if (!confirm('Tem certeza que deseja excluir?')) return
try {
await api.delete(`${endpoints[activeTab]}/${id}`)
fetchItems()
} catch { alert('Erro ao excluir') }
}
const catMap: Record<string, string> = {}
categorias.forEach(c => { catMap[c.id] = c.nome })
const renderFormFields = () => {
switch (activeTab) {
case 'categorias':
return (
<>
<div>
<label className="block text-sm font-medium text-text mb-2">Nome</label>
<input type="text" value={form.nome} onChange={e => setForm({ ...form, nome: e.target.value })} className="input-field" required />
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Tipo de Investimento *</label>
<select value={form.tipo_investimento} onChange={e => setForm({ ...form, tipo_investimento: e.target.value })} className="input-field" required>
<option value="">Selecione...</option>
<option value="capex">Capex</option>
<option value="opex">Opex</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Criticidade Padrão</label>
<select value={form.criticidade_padrao} onChange={e => setForm({ ...form, criticidade_padrao: e.target.value })} className="input-field">
<option value="baixa">Baixa</option>
<option value="media">Média</option>
<option value="alta">Alta</option>
<option value="critica">Crítica</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">SLA (dias)</label>
<input type="number" value={form.sla_dias} onChange={e => setForm({ ...form, sla_dias: Number(e.target.value) })} className="input-field" />
</div>
<div className="col-span-full border-t border-border pt-3 mt-2">
<p className="text-sm font-semibold mb-3" style={{ color: '#1A7A4C' }}>🌿 Campos ESG</p>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Tipo de Manutenção</label>
<select value={form.tipo_manutencao} onChange={e => setForm({ ...form, tipo_manutencao: e.target.value })} className="input-field">
<option value="">Selecione...</option>
<option value="Preventiva">Preventiva</option>
<option value="Corretiva">Corretiva</option>
<option value="Emergencial">Emergencial</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Impacto Ambiental Esperado</label>
<select value={form.impacto_ambiental_esperado} onChange={e => setForm({ ...form, impacto_ambiental_esperado: e.target.value })} className="input-field">
<option value="">Selecione...</option>
<option value="Baixo">Baixo</option>
<option value="Médio">Médio</option>
<option value="Alto">Alto</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Potencial Geração Resíduos</label>
<select value={form.potencial_geracao_residuos} onChange={e => setForm({ ...form, potencial_geracao_residuos: e.target.value })} className="input-field">
<option value="">Selecione...</option>
<option value="Baixo">Baixo</option>
<option value="Médio">Médio</option>
<option value="Alto">Alto</option>
</select>
</div>
</>
)
case 'centros_custo':
return (
<>
<div>
<label className="block text-sm font-medium text-text mb-2">Código</label>
<input type="text" value={form.codigo} onChange={e => setForm({ ...form, codigo: e.target.value })} className="input-field" required />
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Nome</label>
<input type="text" value={form.nome} onChange={e => setForm({ ...form, nome: e.target.value })} className="input-field" required />
</div>
</>
)
case 'subcategorias':
return (
<>
<div>
<label className="block text-sm font-medium text-text mb-2">Nome</label>
<input type="text" value={form.nome} onChange={e => setForm({ ...form, nome: e.target.value })} className="input-field" required />
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Categoria</label>
<select value={form.categoria_id} onChange={e => setForm({ ...form, categoria_id: e.target.value })} className="input-field" required>
<option value="">Selecione...</option>
{categorias.map(c => <option key={c.id} value={c.id}>{c.nome}</option>)}
</select>
</div>
</>
)
case 'locais':
return (
<>
<div>
<label className="block text-sm font-medium text-text mb-2">Nome</label>
<input type="text" value={form.nome} onChange={e => setForm({ ...form, nome: e.target.value })} className="input-field" required />
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Endereço</label>
<input type="text" value={form.endereco} onChange={e => setForm({ ...form, endereco: e.target.value })} className="input-field" />
</div>
<div className="col-span-full border-t border-border pt-3 mt-2">
<p className="text-sm font-semibold mb-3" style={{ color: '#1A7A4C' }}>🌿 Campos ESG</p>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Tipo de Operação</label>
<select value={form.tipo_operacao_local} onChange={e => setForm({ ...form, tipo_operacao_local: e.target.value })} className="input-field">
<option value="">Selecione...</option>
<option value="Administrativo">Administrativo</option>
<option value="Industrial">Industrial</option>
<option value="Logístico">Logístico</option>
<option value="Comercial">Comercial</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Classificação Impacto Ambiental</label>
<select value={form.classificacao_impacto_ambiental} onChange={e => setForm({ ...form, classificacao_impacto_ambiental: e.target.value })} className="input-field">
<option value="">Selecione...</option>
<option value="Baixo">Baixo</option>
<option value="Médio">Médio</option>
<option value="Alto">Alto</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">Práticas Sustentáveis</label>
<div className="space-y-2">
{['Coleta Seletiva', 'Reuso de Água', 'Energia Renovável', 'Compostagem', 'Redução de Plástico'].map(p => (
<label key={p} className="flex items-center gap-2 text-sm">
<input type="checkbox" checked={(form.praticas_sustentaveis || []).includes(p)}
onChange={e => {
const curr = form.praticas_sustentaveis || []
setForm({ ...form, praticas_sustentaveis: e.target.checked ? [...curr, p] : curr.filter((x: string) => x !== p) })
}}
className="rounded border-gray-300 text-green-600" />
{p}
</label>
))}
</div>
</div>
</>
)
}
}
const renderColumns = () => {
switch (activeTab) {
case 'categorias':
return (
<>
<th className="table-cell">Nome</th>
<th className="table-cell">Tipo Invest.</th>
<th className="table-cell">Criticidade</th>
<th className="table-cell">SLA</th>
<th className="table-cell">Manutenção</th>
<th className="table-cell">Impacto Amb.</th>
<th className="table-cell text-center">Ações</th>
</>
)
case 'centros_custo':
return (
<>
<th className="table-cell">Código</th>
<th className="table-cell">Nome</th>
<th className="table-cell text-center">Ações</th>
</>
)
case 'subcategorias':
return (
<>
<th className="table-cell">Nome</th>
<th className="table-cell">Categoria</th>
<th className="table-cell text-center">Ações</th>
</>
)
case 'locais':
return (
<>
<th className="table-cell">Nome</th>
<th className="table-cell">Endereço</th>
<th className="table-cell">Operação</th>
<th className="table-cell">Impacto Amb.</th>
<th className="table-cell text-center">Ações</th>
</>
)
}
}
const renderRow = (item: any) => {
switch (activeTab) {
case 'categorias':
return (
<>
<td className="table-cell font-medium">{item.nome}</td>
<td className="table-cell">
{item.tipo_investimento ? (
<span className={`badge ${item.tipo_investimento === 'capex' ? 'badge-info' : 'badge-warning'}`}>
{item.tipo_investimento === 'capex' ? 'Capex' : 'Opex'}
</span>
) : '-'}
</td>
<td className="table-cell">{item.criticidade_padrao || '-'}</td>
<td className="table-cell">{item.sla_dias} dias</td>
<td className="table-cell">
{item.tipo_manutencao ? (
<span className={`badge ${item.tipo_manutencao === 'Emergencial' ? 'badge-error' : item.tipo_manutencao === 'Corretiva' ? 'badge-warning' : 'badge-success'}`}>
{item.tipo_manutencao}
</span>
) : '-'}
</td>
<td className="table-cell">
{item.impacto_ambiental_esperado ? (
<span className={`badge ${item.impacto_ambiental_esperado === 'Alto' ? 'badge-error' : item.impacto_ambiental_esperado === 'Médio' ? 'badge-warning' : 'badge-success'}`}>
{item.impacto_ambiental_esperado}
</span>
) : '-'}
</td>
</>
)
case 'centros_custo':
return (
<>
<td className="table-cell font-mono">{item.codigo}</td>
<td className="table-cell font-medium">{item.nome}</td>
</>
)
case 'subcategorias':
return (
<>
<td className="table-cell font-medium">{item.nome}</td>
<td className="table-cell">{catMap[item.categoria_id] || '-'}</td>
</>
)
case 'locais':
return (
<>
<td className="table-cell font-medium">{item.nome}</td>
<td className="table-cell">{item.endereco || '-'}</td>
<td className="table-cell">{item.tipo_operacao_local || '-'}</td>
<td className="table-cell">
{item.classificacao_impacto_ambiental ? (
<span className={`badge ${item.classificacao_impacto_ambiental === 'Alto' ? 'badge-error' : item.classificacao_impacto_ambiental === 'Médio' ? 'badge-warning' : 'badge-success'}`}>
{item.classificacao_impacto_ambiental}
</span>
) : '-'}
</td>
</>
)
}
}
return (
<div className="space-y-6 animate-fade-in">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-text flex items-center gap-3">
<Settings className="w-8 h-8 text-primary" /> Configuração
</h1>
<p className="text-gray mt-1">Gerencie as tabelas de apoio do sistema</p>
</div>
<button onClick={openNew} className="btn-primary flex items-center gap-2 w-full sm:w-auto justify-center">
<Plus className="w-5 h-5" />Novo Registro
</button>
</div>
{/* Tabs */}
<div className="flex gap-2 border-b border-border overflow-x-auto">
{tabs.map(tab => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className={`px-4 py-3 text-sm font-medium transition-all border-b-2 whitespace-nowrap ${
activeTab === tab.key
? 'border-primary text-primary'
: 'border-transparent text-gray hover:text-text'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Table */}
{loading ? (
<div className="flex items-center justify-center h-48"><Loader2 className="w-8 h-8 animate-spin text-primary" /></div>
) : (
<div className="card !p-0 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="table-header">
<tr>{renderColumns()}</tr>
</thead>
<tbody>
{items.map(item => (
<tr key={item.id} className="table-row">
{renderRow(item)}
<td className="table-cell">
<div className="flex items-center justify-center gap-1">
<button onClick={() => openEdit(item)} className="p-2 rounded-lg hover:bg-gray-100 text-gray hover:text-primary transition-colors">
<Edit2 className="w-4 h-4" />
</button>
<button onClick={() => handleDelete(item.id)} className="p-2 rounded-lg hover:bg-red-50 text-gray hover:text-red-500 transition-colors">
<Trash2 className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{items.length === 0 && (
<div className="text-center py-12"><p className="text-gray">Nenhum registro encontrado</p></div>
)}
</div>
)}
{/* Create/Edit Modal */}
<Modal open={showModal} onClose={() => setShowModal(false)} title={editId ? 'Editar' : 'Novo Registro'} onSubmit={handleSubmit} submitLabel={editId ? 'Salvar' : 'Criar'} loading={saving}>
{renderFormFields()}
</Modal>
</div>
)
}