Guilherme Silberfarb Costa commited on
Commit
8bd0e6d
·
1 Parent(s): e2ba992

alteracoes nos cards de pesquisa

Browse files
Files changed (35) hide show
  1. backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_004D.dai +3 -0
  2. backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_005.dai +3 -0
  3. backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z4_002N.dai +3 -0
  4. backend/app/core/pesquisa/modelos_dai/MOD_A_DEP_Z1_Z2_Z3_Z4_003B.dai +3 -0
  5. backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1-Z2_001F.dai +3 -0
  6. backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_005D.dai +3 -0
  7. backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_Z2_001E.dai +3 -0
  8. backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_003F.dai +3 -0
  9. backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_004C.dai +3 -0
  10. backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_006B.dai +3 -0
  11. backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_007C.dai +3 -0
  12. backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006B.dai +3 -0
  13. backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006C.dai +2 -2
  14. backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_Z3_001.dai +3 -0
  15. backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_011D.dai +2 -2
  16. backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_020B.dai +2 -2
  17. backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_022.dai +2 -2
  18. backend/app/core/pesquisa/modelos_dai/MOD_V_EDIF_Z1_Z2_Z3_Z4_002E.dai +2 -2
  19. backend/app/core/pesquisa/modelos_dai/MOD_V_RCOND_Z4_004.dai +3 -0
  20. backend/app/core/pesquisa/modelos_dai/MOD_V_SALA_Z1_002E.dai +3 -0
  21. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_GENERICO_2016_2026_001.dai +2 -2
  22. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z1_006L.dai +3 -0
  23. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_008C.dai +2 -2
  24. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013C.dai +3 -0
  25. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013F.dai +3 -0
  26. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_015C_PORTO_SECO.dai +3 -0
  27. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001H.dai +3 -0
  28. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001i.dai +3 -0
  29. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002A.dai +3 -0
  30. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002B.dai +3 -0
  31. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_003J.dai +2 -2
  32. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_016E.dai +3 -0
  33. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z5_007C.dai +3 -0
  34. frontend/src/components/PesquisaTab.jsx +281 -2
  35. frontend/src/styles.css +198 -3
backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_004D.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ff221a8b0b6bec00a00f4c5ec0ff37429943328c943640fc98d3979074eb762c
3
+ size 61298
backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_005.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:32bccd0c76e37878c83697700c3cd1cb1fa2cf8a25b714ef8a3609b031377487
3
+ size 266364
backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z4_002N.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:92939746e3e2a2f1d455de0531c31b893c32415c22161478135321558a79ec8c
3
+ size 78623
backend/app/core/pesquisa/modelos_dai/MOD_A_DEP_Z1_Z2_Z3_Z4_003B.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:77ff58a2844f5c54863eb0b06d8530e48db9af48d5f2255e3ca12d119d19d232
3
+ size 105949
backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1-Z2_001F.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8053723e7c7e917e32709509bfb093ea718a65fec56ca095f0a99252cef69f75
3
+ size 237021
backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_005D.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:df52dbf6e9902c69cf770c2149548b2c13b7f637a6a696b467a49601e5b8a50d
3
+ size 62078
backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_Z2_001E.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:df8c18d84ecc169b41511e2743aca950680ebca0aadeee2b1978c2fd19716871
3
+ size 63789
backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_003F.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:38580da29bd30d5c4cf5a20b008f3a852c6769f3915fa07ff46dbac4bf40be60
3
+ size 124431
backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_004C.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b4bdcc13ef212a014deda190957a7c30beb8f2b42cd3c2518ee9dd0323e87930
3
+ size 120063
backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_006B.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1f6a14fe402b1ec5b3be1492492e01fb0b9050a03e049e22cc80ebbf7b4f88d2
3
+ size 75212
backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_007C.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce78dbb834e496a566e7d695a620ae96261ddc65f9fe397ab3ac0f65718f16e3
3
+ size 82416
backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006B.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d241fad2e540fa24a79edc7ea798ddd22fe456af0ee7e59465c138c10265d6f
3
+ size 122342
backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006C.dai CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:0398acda7bfaa01c782d0b551aa679b21afa85bc3882e5249b5cddceb7b733fd
3
- size 305003
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b7e81622c34e2333ba1a628d7f275a8fa71912f7da8b9720f37298ac285faf0b
3
+ size 305073
backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_Z3_001.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d33b33df63a1d618ab2bcc26c5c111c698e8db9506d34622c0c1ed97fb18ad2c
3
+ size 44681
backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_011D.dai CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:61ff06842995a9e3d559e33c51aaaa584f33477df414ca16abc3861cfc14a6c9
3
- size 5307303
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:49c49b0a46dd373ae7d16307516fa748ae4acc540462570f0c5e567928a29383
3
+ size 5307946
backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_020B.dai CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:319994b70fe36527d8a198c87383b0de819d12a83f5098f4ef89bdfb013fb86f
3
- size 1999486
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f9f69d57ff5949afa8e43e6fe754b86b7d71098db740a70656db63ff003d46db
3
+ size 1999539
backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_022.dai CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e263780042eb5ec056a49ee8862ed9523bebeac831e5ca6a0f7671ec66097413
3
- size 1318536
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:17944f8132fce958a7168d0efc73f4aa07575c16993a16022f89c2d7596c05d0
3
+ size 1318594
backend/app/core/pesquisa/modelos_dai/MOD_V_EDIF_Z1_Z2_Z3_Z4_002E.dai CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:96391e419571bfb37a208a56a969aef8a175e26b11e86f0af99deba40dca18bf
3
- size 1584486
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7d64c816f183f6c18952eacf76fbce781d621d5b5ec5f4b51f40a85b0983e40d
3
+ size 1577576
backend/app/core/pesquisa/modelos_dai/MOD_V_RCOND_Z4_004.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:70e655434ec5441708a1d0f108ae7b8af702cfb7577110db59846c7b4685879e
3
+ size 836586
backend/app/core/pesquisa/modelos_dai/MOD_V_SALA_Z1_002E.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a81098b6a7206d18f23e0676952fa60583283ea84293ea94ad0f84b1414a4210
3
+ size 4622543
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_GENERICO_2016_2026_001.dai CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:33d53230f6ba56a80fb89c4ae4f092e08dc2476bd4ce6a06013c762ec73ca58a
3
- size 19219547
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:30565ca1281c1abe3f224f7309e40479fa20224fe68fade4ce146f804e728c09
3
+ size 19220124
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z1_006L.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:47e009f401dd9644d030f90a0111ac4765bd1054a44027c2fb9f327fca7ad380
3
+ size 1906640
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_008C.dai CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:5f802758c2a569aba28986de3c0dbf1e4c436556a0ba7ee909d6576de6e3f719
3
- size 824046
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ceaae08485539da6b97aded2086fa292a86c1f557cdd7875ba36f0b5f270d60c
3
+ size 824103
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013C.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:46137a963d90e5697ef2e8c01ce5b56af6237ee426fc20662bbf99b14acb2743
3
+ size 1481702
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013F.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cc522cf1e32ed0a3ff25157492accde2c0bc87e2013a2ea8ed3387317317de36
3
+ size 2327452
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_015C_PORTO_SECO.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d71651a7f845c893895bfd6b73c4209b597e1ba78b4c15c0924987f360c89108
3
+ size 3635197
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001H.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:749c833d7cd4190ea7cd0453410c63701746c8ac34b76444f0be55970700b185
3
+ size 1731659
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001i.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fff0145b9990bb251401f85e5c1614fd84936801d6757482443987e1d880a099
3
+ size 1969416
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002A.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:762b80e17c476218ceb1f146e816c6b7a68f0011f35ec27f0535970602c5ea64
3
+ size 1586112
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002B.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3468371b064f43da5b39d992bcccdb8d7c698e5f127e8040932e1bdf40ddcc1d
3
+ size 1998609
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_003J.dai CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9e4fc5f42b69b3f912853ca1bc14ade1246187ce80288b461ea1064c6d743575
3
- size 1559857
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:63059cf719829b1ab76852e6078b4d2808b6da734fa780e429bd852d000a24f6
3
+ size 1559917
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_016E.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ddbe47357238199b28515d42b6cc2730df5b9158b999431515947ae74cd73cdf
3
+ size 5780373
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z5_007C.dai ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:393b92b31396916b0cc500983649592f5ab0bdb6ed6d521b3abed2b9e5cf5235
3
+ size 1372051
frontend/src/components/PesquisaTab.jsx CHANGED
@@ -1,4 +1,5 @@
1
  import React, { useEffect, useMemo, useRef, useState } from 'react'
 
2
  import { api, downloadBlob } from '../api'
3
  import DataTable from './DataTable'
4
  import EquationFormatsPanel from './EquationFormatsPanel'
@@ -123,6 +124,261 @@ function joinMultiTerms(values) {
123
  return (values || []).map((item) => String(item || '').trim()).filter(Boolean).join(' || ')
124
  }
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  function inferTipoPorNomeModelo(...nomes) {
127
  const tokens = Object.keys(TIPO_SIGLAS).sort((a, b) => b.length - a.length)
128
  for (const nome of nomes) {
@@ -1018,6 +1274,9 @@ export default function PesquisaTab({ sessionId, onUsarModeloEmAvaliacao = null
1018
  <div className="pesquisa-card-grid">
1019
  {result.modelos.map((modelo) => {
1020
  const selecionado = selectedIds.includes(modelo.id)
 
 
 
1021
  return (
1022
  <article key={modelo.id} className={`pesquisa-card${selecionado ? ' is-selected' : ''}`}>
1023
  <div className="pesquisa-card-top">
@@ -1039,16 +1298,36 @@ export default function PesquisaTab({ sessionId, onUsarModeloEmAvaliacao = null
1039
  </div>
1040
  <div className="pesquisa-card-body">
1041
  <div className="pesquisa-card-dados-list">
1042
- <div><strong>Finalidades no modelo:</strong> {(modelo.finalidades || []).length ? modelo.finalidades.join(', ') : '-'}</div>
1043
  <div><strong>Tipo:</strong> {formatTipoImovel(modelo)}</div>
1044
  <div><strong>Autor:</strong> {modelo.autor || '-'}</div>
1045
  <div><strong>Dados:</strong> {formatCount(modelo.total_dados)}</div>
1046
  <div><strong>Faixa area:</strong> {formatRange(modelo.faixa_area)}</div>
1047
  <div><strong>Faixa RH:</strong> {formatRange(modelo.faixa_rh)}</div>
1048
  <div><strong>Faixa data:</strong> {formatRange(modelo.faixa_data)}</div>
1049
- <div><strong>Bairros:</strong> {(modelo.bairros || []).length ? (modelo.bairros || []).join(', ') : '-'}</div>
1050
  </div>
1051
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1052
  </div>
1053
 
1054
  {modelo.status !== 'ok' ? <div className="inline-error pesquisa-card-error">{modelo.erro_leitura || 'Falha ao ler modelo.'}</div> : null}
 
1
  import React, { useEffect, useMemo, useRef, useState } from 'react'
2
+ import { createPortal } from 'react-dom'
3
  import { api, downloadBlob } from '../api'
4
  import DataTable from './DataTable'
5
  import EquationFormatsPanel from './EquationFormatsPanel'
 
124
  return (values || []).map((item) => String(item || '').trim()).filter(Boolean).join(' || ')
125
  }
126
 
127
+ function sanitizeCompactList(values) {
128
+ const seen = new Set()
129
+ const unique = []
130
+ ;(values || []).forEach((item) => {
131
+ const text = String(item || '').trim()
132
+ const key = normalizeSearchText(text)
133
+ if (!text || !key || seen.has(key)) return
134
+ seen.add(key)
135
+ unique.push(text)
136
+ })
137
+ return unique
138
+ }
139
+
140
+ function uppercaseListText(values) {
141
+ const items = sanitizeCompactList(values)
142
+ return items.map((item) => item.toLocaleUpperCase('pt-BR')).join(', ')
143
+ }
144
+
145
+ function inferDependentVariable(modelo) {
146
+ const equacao = String(modelo?.equacao || '').trim()
147
+ if (!equacao || !equacao.includes('=')) return ''
148
+ return equacao.split('=', 1)[0].trim()
149
+ }
150
+
151
+ function buildVariablesDisplay(modelo) {
152
+ const dependente = inferDependentVariable(modelo)
153
+ const independentes = sanitizeCompactList((modelo?.variaveis_resumo || []).map((item) => item?.variavel))
154
+ .filter((item) => normalizeSearchText(item) !== normalizeSearchText(dependente))
155
+ .filter((item) => normalizeSearchText(item) !== 'const')
156
+
157
+ const partes = []
158
+ if (dependente) {
159
+ partes.push(`DEPENDENTE: ${dependente.toLocaleUpperCase('pt-BR')}`)
160
+ }
161
+ if (independentes.length) {
162
+ partes.push(`INDEPENDENTES: ${independentes.map((item) => item.toLocaleUpperCase('pt-BR')).join(', ')}`)
163
+ }
164
+ return partes.join('\n')
165
+ }
166
+
167
+ function buildVariablesContent(text) {
168
+ const lines = String(text || '').split('\n').map((item) => item.trim()).filter(Boolean)
169
+ if (!lines.length) return null
170
+
171
+ return (
172
+ <>
173
+ {lines.map((line, index) => {
174
+ const [head, ...rest] = line.split(':')
175
+ const tail = rest.join(':').trim()
176
+ return (
177
+ <span key={`${head}-${index}`} className="pesquisa-card-popover-line">
178
+ <strong>{head}:</strong>{tail ? ` ${tail}` : ''}
179
+ </span>
180
+ )
181
+ })}
182
+ </>
183
+ )
184
+ }
185
+
186
+ function CompactHoverList({
187
+ label,
188
+ buttonLabel,
189
+ previewText,
190
+ modalText,
191
+ previewContent = null,
192
+ modalContent = null,
193
+ }) {
194
+ const rootRef = useRef(null)
195
+ const panelRef = useRef(null)
196
+ const [previewOpen, setPreviewOpen] = useState(false)
197
+ const [modalOpen, setModalOpen] = useState(false)
198
+ const [previewSide, setPreviewSide] = useState('left')
199
+ const [previewVertical, setPreviewVertical] = useState('down')
200
+ const [previewMaxHeight, setPreviewMaxHeight] = useState(160)
201
+ const [previewMaxWidth, setPreviewMaxWidth] = useState(560)
202
+ const inlinePreviewText = String(previewText || '').trim()
203
+ const inlineModalText = String(modalText || '').trim()
204
+
205
+ function updatePreviewLayout() {
206
+ const bounds = rootRef.current?.getBoundingClientRect()
207
+ const panel = panelRef.current
208
+ if (!bounds || !panel || typeof window === 'undefined') return
209
+
210
+ const viewportWidth = window.innerWidth
211
+ const viewportHeight = window.innerHeight
212
+ const gap = 8
213
+ const padding = 16
214
+ const triggerCenter = bounds.left + (bounds.width / 2)
215
+ const viewportCenter = viewportWidth / 2
216
+ const triggerMiddleY = bounds.top + (bounds.height / 2)
217
+ const viewportMiddleY = viewportHeight / 2
218
+
219
+ const preferredSide = triggerCenter >= viewportCenter ? 'left' : 'right'
220
+ const preferredVertical = triggerMiddleY >= viewportMiddleY ? 'up' : 'down'
221
+
222
+ const availableLeft = Math.max(120, bounds.right - padding)
223
+ const availableRight = Math.max(120, viewportWidth - bounds.left - padding)
224
+ const availableAbove = Math.max(96, bounds.top - gap - padding)
225
+ const availableBelow = Math.max(96, viewportHeight - bounds.bottom - gap - padding)
226
+
227
+ const desiredWidth = Math.min(panel.scrollWidth || 560, 560)
228
+ const desiredHeight = Math.min(panel.scrollHeight || 160, 320)
229
+
230
+ let nextSide = preferredSide
231
+ if (preferredSide === 'left' && desiredWidth > availableLeft && availableRight > availableLeft) {
232
+ nextSide = 'right'
233
+ } else if (preferredSide === 'right' && desiredWidth > availableRight && availableLeft > availableRight) {
234
+ nextSide = 'left'
235
+ }
236
+
237
+ let nextVertical = preferredVertical
238
+ if (preferredVertical === 'up' && desiredHeight > availableAbove && availableBelow > availableAbove) {
239
+ nextVertical = 'down'
240
+ } else if (preferredVertical === 'down' && desiredHeight > availableBelow && availableAbove > availableBelow) {
241
+ nextVertical = 'up'
242
+ }
243
+
244
+ const nextMaxWidth = nextSide === 'left' ? availableLeft : availableRight
245
+ const nextMaxHeight = nextVertical === 'up' ? availableAbove : availableBelow
246
+
247
+ setPreviewSide(nextSide)
248
+ setPreviewVertical(nextVertical)
249
+ setPreviewMaxWidth(nextMaxWidth)
250
+ setPreviewMaxHeight(nextMaxHeight)
251
+ }
252
+
253
+ useEffect(() => {
254
+ if (!previewOpen) return undefined
255
+
256
+ const rafId = window.requestAnimationFrame(() => {
257
+ updatePreviewLayout()
258
+ })
259
+
260
+ function onWindowResize() {
261
+ updatePreviewLayout()
262
+ }
263
+
264
+ window.addEventListener('resize', onWindowResize)
265
+ return () => {
266
+ window.cancelAnimationFrame(rafId)
267
+ window.removeEventListener('resize', onWindowResize)
268
+ }
269
+ }, [previewOpen])
270
+
271
+ useEffect(() => {
272
+ if (!modalOpen) return undefined
273
+
274
+ function onDocumentKeyDown(event) {
275
+ if (event.key === 'Escape') {
276
+ setModalOpen(false)
277
+ }
278
+ }
279
+
280
+ document.addEventListener('keydown', onDocumentKeyDown)
281
+ return () => {
282
+ document.removeEventListener('keydown', onDocumentKeyDown)
283
+ }
284
+ }, [modalOpen])
285
+
286
+ if (!inlineModalText) {
287
+ return (
288
+ <button type="button" className="pesquisa-card-popover-trigger is-empty" disabled>
289
+ {buttonLabel}
290
+ </button>
291
+ )
292
+ }
293
+
294
+ return (
295
+ <>
296
+ <span
297
+ ref={rootRef}
298
+ className={`pesquisa-card-popover-wrap${previewOpen ? ' is-open' : ''}`}
299
+ data-preview-side={previewSide}
300
+ data-preview-vertical={previewVertical}
301
+ onMouseEnter={() => {
302
+ setPreviewOpen(true)
303
+ }}
304
+ onMouseLeave={() => setPreviewOpen(false)}
305
+ onFocus={() => {
306
+ setPreviewOpen(true)
307
+ }}
308
+ onBlur={(event) => {
309
+ if (!rootRef.current?.contains(event.relatedTarget)) {
310
+ setPreviewOpen(false)
311
+ }
312
+ }}
313
+ >
314
+ <button
315
+ type="button"
316
+ className="pesquisa-card-popover-trigger"
317
+ aria-haspopup="dialog"
318
+ aria-expanded={modalOpen}
319
+ aria-label={`${label}. Clique para abrir a lista completa.`}
320
+ onClick={() => {
321
+ setPreviewOpen(false)
322
+ setModalOpen(true)
323
+ }}
324
+ >
325
+ {buttonLabel}
326
+ </button>
327
+ <span
328
+ ref={panelRef}
329
+ className="pesquisa-card-popover-panel"
330
+ role="tooltip"
331
+ aria-label={label}
332
+ style={{
333
+ maxWidth: `${Math.max(120, previewMaxWidth)}px`,
334
+ maxHeight: `${Math.max(96, previewMaxHeight)}px`,
335
+ }}
336
+ >
337
+ <span className="pesquisa-card-popover-preview">{previewContent || inlinePreviewText || inlineModalText}</span>
338
+ </span>
339
+ </span>
340
+ {modalOpen && typeof document !== 'undefined'
341
+ ? createPortal(
342
+ <div
343
+ className="pesquisa-modal-backdrop"
344
+ role="presentation"
345
+ onClick={() => {
346
+ setModalOpen(false)
347
+ }}
348
+ >
349
+ <div
350
+ className="pesquisa-modal pesquisa-card-values-modal"
351
+ role="dialog"
352
+ aria-modal="true"
353
+ aria-label={label}
354
+ onClick={(event) => event.stopPropagation()}
355
+ >
356
+ <div className="pesquisa-modal-head">
357
+ <h4>{label}</h4>
358
+ <div className="pesquisa-card-values-modal-actions">
359
+ <button
360
+ type="button"
361
+ className="pesquisa-modal-close"
362
+ onClick={() => {
363
+ setModalOpen(false)
364
+ }}
365
+ >
366
+ Fechar
367
+ </button>
368
+ </div>
369
+ </div>
370
+ <div className="pesquisa-modal-body">
371
+ <div className="pesquisa-card-values-content">{modalContent || inlineModalText}</div>
372
+ </div>
373
+ </div>
374
+ </div>,
375
+ document.body,
376
+ )
377
+ : null}
378
+ </>
379
+ )
380
+ }
381
+
382
  function inferTipoPorNomeModelo(...nomes) {
383
  const tokens = Object.keys(TIPO_SIGLAS).sort((a, b) => b.length - a.length)
384
  for (const nome of nomes) {
 
1274
  <div className="pesquisa-card-grid">
1275
  {result.modelos.map((modelo) => {
1276
  const selecionado = selectedIds.includes(modelo.id)
1277
+ const finalidadesText = uppercaseListText(modelo.finalidades || [])
1278
+ const bairrosText = uppercaseListText(modelo.bairros || [])
1279
+ const variaveisText = buildVariablesDisplay(modelo)
1280
  return (
1281
  <article key={modelo.id} className={`pesquisa-card${selecionado ? ' is-selected' : ''}`}>
1282
  <div className="pesquisa-card-top">
 
1298
  </div>
1299
  <div className="pesquisa-card-body">
1300
  <div className="pesquisa-card-dados-list">
 
1301
  <div><strong>Tipo:</strong> {formatTipoImovel(modelo)}</div>
1302
  <div><strong>Autor:</strong> {modelo.autor || '-'}</div>
1303
  <div><strong>Dados:</strong> {formatCount(modelo.total_dados)}</div>
1304
  <div><strong>Faixa area:</strong> {formatRange(modelo.faixa_area)}</div>
1305
  <div><strong>Faixa RH:</strong> {formatRange(modelo.faixa_rh)}</div>
1306
  <div><strong>Faixa data:</strong> {formatRange(modelo.faixa_data)}</div>
 
1307
  </div>
1308
  </div>
1309
+ <div className="pesquisa-card-meta-actions">
1310
+ <CompactHoverList
1311
+ label="Finalidades"
1312
+ buttonLabel="Finalidades"
1313
+ previewText={finalidadesText}
1314
+ modalText={finalidadesText}
1315
+ />
1316
+ <CompactHoverList
1317
+ label="Bairros"
1318
+ buttonLabel="Bairros"
1319
+ previewText={bairrosText}
1320
+ modalText={bairrosText}
1321
+ />
1322
+ <CompactHoverList
1323
+ label="Variáveis"
1324
+ buttonLabel="Variáveis"
1325
+ previewText={variaveisText}
1326
+ modalText={variaveisText}
1327
+ previewContent={buildVariablesContent(variaveisText)}
1328
+ modalContent={buildVariablesContent(variaveisText)}
1329
+ />
1330
+ </div>
1331
  </div>
1332
 
1333
  {modelo.status !== 'ok' ? <div className="inline-error pesquisa-card-error">{modelo.erro_leitura || 'Falha ao ler modelo.'}</div> : null}
frontend/src/styles.css CHANGED
@@ -1938,14 +1938,18 @@ button.pesquisa-coluna-remove:hover {
1938
  gap: 10px;
1939
  min-width: 0;
1940
  height: 100%;
1941
- overflow: hidden;
 
 
1942
  box-shadow:
1943
  0 6px 18px rgba(26, 43, 61, 0.06),
1944
  inset 0 0 0 1px rgba(255, 255, 255, 0.75);
1945
  transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
1946
  }
1947
 
1948
- .pesquisa-card:hover {
 
 
1949
  transform: translateY(-1px);
1950
  border-color: #c8dced;
1951
  box-shadow:
@@ -1984,6 +1988,14 @@ button.pesquisa-coluna-remove:hover {
1984
  min-width: 0;
1985
  }
1986
 
 
 
 
 
 
 
 
 
1987
  .pesquisa-card-actions button {
1988
  display: inline-flex;
1989
  align-items: center;
@@ -2045,6 +2057,7 @@ button.pesquisa-coluna-remove:hover {
2045
  display: grid;
2046
  gap: 9px;
2047
  min-width: 0;
 
2048
  border-top: 1px solid #e7eef5;
2049
  padding-top: 9px;
2050
  }
@@ -2075,6 +2088,139 @@ button.pesquisa-coluna-remove:hover {
2075
  line-height: 1.34;
2076
  }
2077
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2078
  .pesquisa-card-bairros {
2079
  padding-top: 2px;
2080
  font-size: 0.82rem;
@@ -2154,6 +2300,32 @@ button.pesquisa-coluna-remove:hover {
2154
  margin-top: 12px;
2155
  }
2156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2157
  .pesquisa-card-error {
2158
  margin-top: 2px;
2159
  }
@@ -5341,10 +5513,20 @@ button.btn-download-subtle {
5341
  scrollbar-width: thin;
5342
  }
5343
 
5344
- .pesquisa-card-actions {
 
5345
  grid-template-columns: 1fr;
5346
  }
5347
 
 
 
 
 
 
 
 
 
 
5348
  .pesquisa-results-toolbar {
5349
  align-items: flex-start;
5350
  }
@@ -5366,6 +5548,19 @@ button.btn-download-subtle {
5366
  flex-direction: column;
5367
  }
5368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5369
  .variavel-badge-line {
5370
  grid-template-columns: 1fr;
5371
  gap: 5px;
 
1938
  gap: 10px;
1939
  min-width: 0;
1940
  height: 100%;
1941
+ overflow: visible;
1942
+ position: relative;
1943
+ z-index: 0;
1944
  box-shadow:
1945
  0 6px 18px rgba(26, 43, 61, 0.06),
1946
  inset 0 0 0 1px rgba(255, 255, 255, 0.75);
1947
  transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
1948
  }
1949
 
1950
+ .pesquisa-card:hover,
1951
+ .pesquisa-card:focus-within {
1952
+ z-index: 3;
1953
  transform: translateY(-1px);
1954
  border-color: #c8dced;
1955
  box-shadow:
 
1988
  min-width: 0;
1989
  }
1990
 
1991
+ .pesquisa-card-meta-actions {
1992
+ display: grid;
1993
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1994
+ gap: 8px;
1995
+ min-width: 0;
1996
+ margin-top: auto;
1997
+ }
1998
+
1999
  .pesquisa-card-actions button {
2000
  display: inline-flex;
2001
  align-items: center;
 
2057
  display: grid;
2058
  gap: 9px;
2059
  min-width: 0;
2060
+ flex: 1 1 auto;
2061
  border-top: 1px solid #e7eef5;
2062
  padding-top: 9px;
2063
  }
 
2088
  line-height: 1.34;
2089
  }
2090
 
2091
+ .pesquisa-card-popover-row {
2092
+ display: flex;
2093
+ align-items: flex-start;
2094
+ justify-content: space-between;
2095
+ gap: 10px;
2096
+ }
2097
+
2098
+ .pesquisa-card-popover-row strong {
2099
+ flex: 0 1 auto;
2100
+ }
2101
+
2102
+ .pesquisa-card-popover-wrap {
2103
+ position: relative;
2104
+ display: flex;
2105
+ min-width: 0;
2106
+ }
2107
+
2108
+ .pesquisa-card-meta-actions .pesquisa-card-popover-wrap {
2109
+ width: 100%;
2110
+ }
2111
+
2112
+ .pesquisa-card-popover-empty {
2113
+ color: #5b7086;
2114
+ }
2115
+
2116
+ .pesquisa-card-popover-trigger {
2117
+ display: inline-flex;
2118
+ align-items: center;
2119
+ justify-content: center;
2120
+ min-height: 28px;
2121
+ padding: 4px 10px;
2122
+ border: 1px solid #cddceb;
2123
+ border-radius: 999px;
2124
+ background: linear-gradient(180deg, #f6fbff 0%, #edf5fb 100%);
2125
+ color: #35506a;
2126
+ font-size: 0.74rem;
2127
+ font-weight: 700;
2128
+ line-height: 1.1;
2129
+ white-space: nowrap;
2130
+ box-shadow: 0 4px 10px rgba(42, 68, 95, 0.08);
2131
+ overflow: hidden;
2132
+ text-overflow: ellipsis;
2133
+ }
2134
+
2135
+ .pesquisa-card-meta-actions .pesquisa-card-popover-trigger,
2136
+ .pesquisa-card-meta-actions .pesquisa-card-popover-trigger.is-empty {
2137
+ width: 100%;
2138
+ }
2139
+
2140
+ .pesquisa-card-popover-trigger.is-empty {
2141
+ opacity: 0.5;
2142
+ cursor: default;
2143
+ }
2144
+
2145
+ .pesquisa-card-popover-trigger:hover,
2146
+ .pesquisa-card-popover-trigger:focus-visible {
2147
+ border-color: #abc5dd;
2148
+ background: linear-gradient(180deg, #ffffff 0%, #eef6fd 100%);
2149
+ box-shadow: 0 7px 16px rgba(42, 68, 95, 0.12);
2150
+ outline: none;
2151
+ }
2152
+
2153
+ .pesquisa-card-popover-panel {
2154
+ position: absolute;
2155
+ top: calc(100% + 8px);
2156
+ right: 0;
2157
+ display: block;
2158
+ width: min(560px, calc(100vw - 56px));
2159
+ max-height: 160px;
2160
+ padding: 10px 12px;
2161
+ border: 1px solid #000;
2162
+ border-radius: 10px;
2163
+ background: rgba(255, 255, 255, 0.98);
2164
+ box-shadow: 0 14px 26px rgba(24, 39, 55, 0.14);
2165
+ backdrop-filter: blur(8px);
2166
+ opacity: 0;
2167
+ visibility: hidden;
2168
+ transform: translateY(6px);
2169
+ pointer-events: none;
2170
+ transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s ease;
2171
+ }
2172
+
2173
+ .pesquisa-card-popover-wrap[data-preview-vertical='up'] .pesquisa-card-popover-panel {
2174
+ top: auto;
2175
+ bottom: calc(100% + 8px);
2176
+ transform: translateY(-6px);
2177
+ }
2178
+
2179
+ .pesquisa-card-popover-wrap[data-preview-vertical='down'] .pesquisa-card-popover-panel {
2180
+ top: calc(100% + 8px);
2181
+ bottom: auto;
2182
+ transform: translateY(6px);
2183
+ }
2184
+
2185
+ .pesquisa-card-popover-wrap[data-preview-side='right'] .pesquisa-card-popover-panel {
2186
+ left: 0;
2187
+ right: auto;
2188
+ }
2189
+
2190
+ .pesquisa-card-popover-wrap[data-preview-side='left'] .pesquisa-card-popover-panel {
2191
+ right: 0;
2192
+ left: auto;
2193
+ }
2194
+
2195
+ .pesquisa-card-popover-wrap:hover .pesquisa-card-popover-panel,
2196
+ .pesquisa-card-popover-wrap:focus-within .pesquisa-card-popover-panel,
2197
+ .pesquisa-card-popover-wrap.is-open .pesquisa-card-popover-panel {
2198
+ opacity: 1;
2199
+ visibility: visible;
2200
+ transform: translateY(0);
2201
+ pointer-events: auto;
2202
+ }
2203
+
2204
+ .pesquisa-card-popover-preview {
2205
+ display: block;
2206
+ overflow: auto;
2207
+ min-width: 0;
2208
+ color: #405a73;
2209
+ font-size: 0.74rem;
2210
+ line-height: 1.5;
2211
+ text-align: left;
2212
+ white-space: pre-wrap;
2213
+ overflow-wrap: anywhere;
2214
+ }
2215
+
2216
+ .pesquisa-card-popover-line {
2217
+ display: block;
2218
+ }
2219
+
2220
+ .pesquisa-card-popover-line strong {
2221
+ color: #24384d;
2222
+ }
2223
+
2224
  .pesquisa-card-bairros {
2225
  padding-top: 2px;
2226
  font-size: 0.82rem;
 
2300
  margin-top: 12px;
2301
  }
2302
 
2303
+ .pesquisa-card-values-modal {
2304
+ width: fit-content;
2305
+ max-width: min(860px, calc(100vw - 40px));
2306
+ }
2307
+
2308
+ .pesquisa-card-values-modal-actions {
2309
+ display: flex;
2310
+ align-items: center;
2311
+ gap: 8px;
2312
+ justify-content: flex-end;
2313
+ }
2314
+
2315
+ .pesquisa-card-values-content {
2316
+ max-width: min(800px, calc(100vw - 72px));
2317
+ padding: 12px 14px;
2318
+ border: 1px solid #d4e0eb;
2319
+ border-radius: 12px;
2320
+ background: #f8fbfe;
2321
+ color: #2f455a;
2322
+ font-size: 0.83rem;
2323
+ line-height: 1.55;
2324
+ white-space: pre-wrap;
2325
+ overflow-wrap: anywhere;
2326
+ user-select: text;
2327
+ }
2328
+
2329
  .pesquisa-card-error {
2330
  margin-top: 2px;
2331
  }
 
5513
  scrollbar-width: thin;
5514
  }
5515
 
5516
+ .pesquisa-card-actions,
5517
+ .pesquisa-card-meta-actions {
5518
  grid-template-columns: 1fr;
5519
  }
5520
 
5521
+ .pesquisa-card-popover-trigger {
5522
+ width: 100%;
5523
+ justify-content: center;
5524
+ }
5525
+
5526
+ .pesquisa-card-popover-panel {
5527
+ width: min(100%, 560px);
5528
+ }
5529
+
5530
  .pesquisa-results-toolbar {
5531
  align-items: flex-start;
5532
  }
 
5548
  flex-direction: column;
5549
  }
5550
 
5551
+ .pesquisa-card-values-modal-actions {
5552
+ width: 100%;
5553
+ justify-content: flex-end;
5554
+ }
5555
+
5556
+ .pesquisa-card-values-modal-actions button {
5557
+ flex: 0 0 auto;
5558
+ }
5559
+
5560
+ .pesquisa-card-values-content {
5561
+ max-width: calc(100vw - 52px);
5562
+ }
5563
+
5564
  .variavel-badge-line {
5565
  grid-template-columns: 1fr;
5566
  gap: 5px;