Spaces:
Running
Running
| 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> | |
| ) | |
| } | |