mesa-react / frontend /src /components /PesquisaAdminConfigPanel.jsx
Guilherme Silberfarb Costa
diversas atualizacoes na aba pesquisas e na estrutura do app
2e13456
import React, { useEffect, useMemo, useState } from 'react'
import { api } from '../api'
const CAMPOS_UNIFICADOS = [
{ id: 'finalidade', label: 'Finalidade', targets: ['aval_finalidade'] },
{ id: 'bairros', label: 'Bairro', targets: ['aval_bairro'] },
{ id: 'data', label: 'Data', targets: ['data'] },
{ id: 'area', label: 'Area', targets: ['aval_area'] },
{ id: 'rh', label: 'RH', targets: ['aval_rh'] },
]
function sortByLabel(items = []) {
return [...items].sort((a, b) => a.label.localeCompare(b.label, 'pt-BR', { sensitivity: 'base' }))
}
function normalizeColunasConfig(rawConfig = {}) {
const out = {}
CAMPOS_UNIFICADOS.forEach(({ id, targets }) => {
const disponiveisMap = new Map()
const padrao = []
const padraoVistos = new Set()
targets.forEach((target) => {
const config = rawConfig?.[target] || {}
;(Array.isArray(config?.disponiveis) ? config.disponiveis : []).forEach((item) => {
const itemId = typeof item === 'string' ? item : item?.id
const itemLabel = typeof item === 'string' ? item : item?.label || item?.id
const idText = String(itemId || '').trim()
if (!idText) return
if (!disponiveisMap.has(idText)) {
disponiveisMap.set(idText, String(itemLabel || idText))
}
})
})
const disponiveis = sortByLabel(Array.from(disponiveisMap.entries()).map(([itemId, label]) => ({ id: itemId, label })))
const idsDisponiveis = new Set(disponiveis.map((item) => item.id))
targets.forEach((target) => {
const config = rawConfig?.[target] || {}
;(Array.isArray(config?.padrao) ? config.padrao : []).forEach((item) => {
const idText = String(item || '').trim()
if (!idText || !idsDisponiveis.has(idText) || padraoVistos.has(idText)) return
padraoVistos.add(idText)
padrao.push(idText)
})
})
if (!padrao.length) {
disponiveis.forEach((item) => padrao.push(item.id))
}
out[id] = { disponiveis, padrao }
})
return out
}
function normalizeSelecionadas(raw = {}, configNormalizada = {}) {
const out = {}
CAMPOS_UNIFICADOS.forEach(({ id, targets }) => {
const disponiveis = new Set((configNormalizada[id]?.disponiveis || []).map((item) => item.id))
const preferidas = []
targets.forEach((target) => {
;(Array.isArray(raw?.[target]) ? raw[target] : []).forEach((item) => preferidas.push(String(item || '').trim()))
})
const validas = preferidas.filter((item, idx) => item && disponiveis.has(item) && preferidas.indexOf(item) === idx)
if (validas.length) {
out[id] = validas
return
}
const padrao = (configNormalizada[id]?.padrao || []).filter((item) => disponiveis.has(item))
out[id] = Array.from(new Set(padrao))
})
return out
}
function buildPayload(selecionadas = {}, colunasConfig = {}) {
const payload = {}
CAMPOS_UNIFICADOS.forEach(({ id, targets }) => {
const idsDisponiveis = new Set((colunasConfig[id]?.disponiveis || []).map((item) => item.id))
const selecionadasCampo = (selecionadas[id] || [])
.map((item) => String(item || '').trim())
.filter((item, idx, arr) => item && idsDisponiveis.has(item) && arr.indexOf(item) === idx)
targets.forEach((target) => {
payload[target] = [...selecionadasCampo]
})
})
return payload
}
function serializarCampos(campos = {}) {
const normalizado = {}
Object.keys(campos || {}).sort().forEach((campo) => {
normalizado[campo] = [...(campos[campo] || [])]
})
return JSON.stringify(normalizado)
}
export default function PesquisaAdminConfigPanel({ onSaved }) {
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
const [status, setStatus] = useState('')
const [colunasConfig, setColunasConfig] = useState({})
const [selecionadas, setSelecionadas] = useState({})
const [baseline, setBaseline] = useState('{}')
const dirty = useMemo(() => serializarCampos(selecionadas) !== baseline, [selecionadas, baseline])
async function carregar() {
setLoading(true)
setError('')
setStatus('')
try {
const response = await api.pesquisaAdminConfig()
const configNormalizada = normalizeColunasConfig(response.colunas_filtro || {})
const selecionadasNormalizadas = normalizeSelecionadas(response.admin_fontes || {}, configNormalizada)
const base = serializarCampos(selecionadasNormalizadas)
setColunasConfig(configNormalizada)
setSelecionadas(selecionadasNormalizadas)
setBaseline(base)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
useEffect(() => {
void carregar()
}, [])
function findLabel(campo, id) {
const match = (colunasConfig[campo]?.disponiveis || []).find((item) => item.id === id)
return match?.label || id
}
function onAdd(campo, value) {
const id = String(value || '').trim()
if (!id) return
setSelecionadas((current) => {
const atual = current[campo] || []
if (atual.includes(id)) return current
return { ...current, [campo]: [...atual, id] }
})
}
function onRemove(campo, id) {
setSelecionadas((current) => ({
...current,
[campo]: (current[campo] || []).filter((item) => item !== id),
}))
}
function onRestaurarPadrao(campo) {
const padrao = colunasConfig[campo]?.padrao || []
setSelecionadas((current) => ({ ...current, [campo]: [...padrao] }))
}
async function onSalvar() {
setSaving(true)
setError('')
setStatus('')
try {
const response = await api.pesquisaAdminConfigSalvar(buildPayload(selecionadas, colunasConfig))
const configNormalizada = normalizeColunasConfig(response.colunas_filtro || {})
const selecionadasNormalizadas = normalizeSelecionadas(response.admin_fontes || {}, configNormalizada)
const base = serializarCampos(selecionadasNormalizadas)
setColunasConfig(configNormalizada)
setSelecionadas(selecionadasNormalizadas)
setBaseline(base)
setStatus(response.status || 'Configuracao salva.')
if (onSaved) onSaved()
} catch (err) {
setError(err.message)
} finally {
setSaving(false)
}
}
function renderCampo(campo, label) {
const configCampo = colunasConfig[campo] || { disponiveis: [], padrao: [] }
const selecionadasCampo = selecionadas[campo] || []
const selectedSet = new Set(selecionadasCampo)
const opcoesAdicionar = (configCampo.disponiveis || []).filter((item) => !selectedSet.has(item.id))
return (
<div key={campo} className="pesquisa-admin-field">
<div className="pesquisa-admin-field-head">
<strong>{label}</strong>
<button type="button" className="btn-pesquisa-expand" onClick={() => onRestaurarPadrao(campo)}>
Restaurar padrao
</button>
</div>
<div className="pesquisa-dynamic-filter-row pesquisa-admin-row">
<div className="pesquisa-colunas-box">
<div className="pesquisa-colunas-chip-list">
{selecionadasCampo.map((itemId) => (
<span key={`${campo}-${itemId}`} className="pesquisa-coluna-chip">
<span>{findLabel(campo, itemId)}</span>
<button type="button" className="pesquisa-coluna-remove" onClick={() => onRemove(campo, itemId)} aria-label={`Remover fonte ${findLabel(campo, itemId)}`}>
x
</button>
</span>
))}
{!selecionadasCampo.length ? <span className="pesquisa-colunas-empty">Nenhuma fonte selecionada.</span> : null}
</div>
</div>
<select
className="pesquisa-colunas-add"
defaultValue=""
onChange={(event) => {
const selected = String(event.target.value || '').trim()
if (!selected) return
onAdd(campo, selected)
event.target.value = ''
}}
>
<option value="">Adicionar fonte...</option>
{opcoesAdicionar.map((item) => (
<option key={`${campo}-opt-${item.id}`} value={item.id}>{item.label}</option>
))}
</select>
</div>
</div>
)
}
return (
<div className="pesquisa-admin-panel">
<div className="row pesquisa-actions pesquisa-actions-primary">
<button type="button" onClick={() => void onSalvar()} disabled={saving || loading || !dirty}>
{saving ? 'Salvando...' : 'Salvar configuracao'}
</button>
<button type="button" onClick={() => void carregar()} disabled={saving || loading}>
Recarregar
</button>
</div>
{status ? <div className="status-line">{status}</div> : null}
{error ? <div className="error-line inline-error">{error}</div> : null}
<div className="pesquisa-admin-fields">
{CAMPOS_UNIFICADOS.map((campo) => renderCampo(campo.id, campo.label))}
</div>
</div>
)
}