Guilherme Silberfarb Costa commited on
Commit
696b48a
·
1 Parent(s): fce4fe8

estilo de tabelas

Browse files
frontend/src/components/AvaliacaoTab.jsx CHANGED
@@ -4,6 +4,7 @@ import { buildCsvBlob } from '../csv'
4
  import LoadingOverlay from './LoadingOverlay'
5
  import MapFrame from './MapFrame'
6
  import SinglePillAutocomplete from './SinglePillAutocomplete'
 
7
 
8
  function normalizarChaveModelo(value) {
9
  return String(value || '')
@@ -61,6 +62,16 @@ function formatarValorTabelaKnn(coluna, valor) {
61
  return String(valor)
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
64
  function temCoordenadasAvaliandoKnn(itens) {
65
  if (!Array.isArray(itens) || !itens.length) return false
66
  const variaveis = new Set(
@@ -545,6 +556,15 @@ export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
545
  return avaliacoesCards.find((item) => item.id === baseCardId) || null
546
  }, [avaliacoesCards, baseCardId])
547
 
 
 
 
 
 
 
 
 
 
548
  function resolverModeloIdRepositorio(chaveBruta, modelosOverride = null) {
549
  const chave = String(chaveBruta || '').trim()
550
  if (!chave) return ''
@@ -1842,7 +1862,15 @@ export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
1842
  <thead>
1843
  <tr>
1844
  {knnDetalheTabela.columns.map((coluna) => (
1845
- <th key={`th-knn-${coluna}`}>{String(coluna)}</th>
 
 
 
 
 
 
 
 
1846
  ))}
1847
  </tr>
1848
  </thead>
@@ -1850,8 +1878,19 @@ export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
1850
  {(knnDetalheTabela.rows || []).map((linha, idxLinha) => (
1851
  <tr key={`tr-knn-${idxLinha}`}>
1852
  {knnDetalheTabela.columns.map((coluna) => (
1853
- <td key={`td-knn-${idxLinha}-${coluna}`}>
1854
- {formatarValorTabelaKnn(coluna, linha?.[coluna])}
 
 
 
 
 
 
 
 
 
 
 
1855
  </td>
1856
  ))}
1857
  </tr>
 
4
  import LoadingOverlay from './LoadingOverlay'
5
  import MapFrame from './MapFrame'
6
  import SinglePillAutocomplete from './SinglePillAutocomplete'
7
+ import TruncatedCellContent from './TruncatedCellContent'
8
 
9
  function normalizarChaveModelo(value) {
10
  return String(value || '')
 
62
  return String(valor)
63
  }
64
 
65
+ function estimarLarguraColunaKnn(coluna, rows = []) {
66
+ const larguraBase = String(coluna || '').trim().length
67
+ const maiorConteudo = (rows || []).reduce((maximo, row) => {
68
+ const texto = String(formatarValorTabelaKnn(coluna, row?.[coluna]) || '').trim()
69
+ return Math.max(maximo, texto.length)
70
+ }, larguraBase)
71
+ const larguraSugerida = Math.ceil(maiorConteudo / 3) + 2
72
+ return Math.max(12, Math.min(34, larguraSugerida))
73
+ }
74
+
75
  function temCoordenadasAvaliandoKnn(itens) {
76
  if (!Array.isArray(itens) || !itens.length) return false
77
  const variaveis = new Set(
 
556
  return avaliacoesCards.find((item) => item.id === baseCardId) || null
557
  }, [avaliacoesCards, baseCardId])
558
 
559
+ const knnDetalheColunasLargura = useMemo(() => {
560
+ const columns = Array.isArray(knnDetalheTabela?.columns) ? knnDetalheTabela.columns : []
561
+ const rows = Array.isArray(knnDetalheTabela?.rows) ? knnDetalheTabela.rows : []
562
+ return columns.reduce((acc, coluna) => {
563
+ acc[coluna] = estimarLarguraColunaKnn(coluna, rows)
564
+ return acc
565
+ }, {})
566
+ }, [knnDetalheTabela])
567
+
568
  function resolverModeloIdRepositorio(chaveBruta, modelosOverride = null) {
569
  const chave = String(chaveBruta || '').trim()
570
  if (!chave) return ''
 
1862
  <thead>
1863
  <tr>
1864
  {knnDetalheTabela.columns.map((coluna) => (
1865
+ <th
1866
+ key={`th-knn-${coluna}`}
1867
+ style={{
1868
+ minWidth: `${knnDetalheColunasLargura[coluna] || 12}ch`,
1869
+ width: `${knnDetalheColunasLargura[coluna] || 12}ch`,
1870
+ }}
1871
+ >
1872
+ {String(coluna)}
1873
+ </th>
1874
  ))}
1875
  </tr>
1876
  </thead>
 
1878
  {(knnDetalheTabela.rows || []).map((linha, idxLinha) => (
1879
  <tr key={`tr-knn-${idxLinha}`}>
1880
  {knnDetalheTabela.columns.map((coluna) => (
1881
+ <td
1882
+ key={`td-knn-${idxLinha}-${coluna}`}
1883
+ style={{
1884
+ minWidth: `${knnDetalheColunasLargura[coluna] || 12}ch`,
1885
+ width: `${knnDetalheColunasLargura[coluna] || 12}ch`,
1886
+ }}
1887
+ >
1888
+ <TruncatedCellContent
1889
+ className="avaliacao-knn-table-cell"
1890
+ tooltipContent={String(formatarValorTabelaKnn(coluna, linha?.[coluna]))}
1891
+ >
1892
+ {formatarValorTabelaKnn(coluna, linha?.[coluna])}
1893
+ </TruncatedCellContent>
1894
  </td>
1895
  ))}
1896
  </tr>
frontend/src/components/DataTable.jsx CHANGED
@@ -1,7 +1,8 @@
1
  import React from 'react'
 
2
 
3
  const LIMIAR_RENDERIZACAO_VIRTUAL = 1500
4
- const ESTIMATIVA_ALTURA_LINHA_PX = 33
5
  const OVERSCAN_LINHAS = 40
6
  const MIN_JANELA_LINHAS = 220
7
  const SORT_COLLATOR = new Intl.Collator('pt-BR', { numeric: true, sensitivity: 'base' })
@@ -205,7 +206,11 @@ function DataTable({
205
  return (
206
  <tr key={absoluteIndex} className={rowClassName}>
207
  {columns.map((col) => (
208
- <td key={`${absoluteIndex}-${col}`}>{String(row[col] ?? '')}</td>
 
 
 
 
209
  ))}
210
  </tr>
211
  )
 
1
  import React from 'react'
2
+ import TruncatedCellContent from './TruncatedCellContent'
3
 
4
  const LIMIAR_RENDERIZACAO_VIRTUAL = 1500
5
+ const ESTIMATIVA_ALTURA_LINHA_PX = 68
6
  const OVERSCAN_LINHAS = 40
7
  const MIN_JANELA_LINHAS = 220
8
  const SORT_COLLATOR = new Intl.Collator('pt-BR', { numeric: true, sensitivity: 'base' })
 
206
  return (
207
  <tr key={absoluteIndex} className={rowClassName}>
208
  {columns.map((col) => (
209
+ <td key={`${absoluteIndex}-${col}`}>
210
+ <TruncatedCellContent tooltipContent={String(row[col] ?? '')}>
211
+ {String(row[col] ?? '')}
212
+ </TruncatedCellContent>
213
+ </td>
214
  ))}
215
  </tr>
216
  )
frontend/src/components/ElaboracaoTab.jsx CHANGED
@@ -9,6 +9,7 @@ import MapFrame from './MapFrame'
9
  import PlotFigure from './PlotFigure'
10
  import SectionBlock from './SectionBlock'
11
  import SinglePillAutocomplete from './SinglePillAutocomplete'
 
12
 
13
  const OPERADORES = ['<=', '>=', '<', '>', '=']
14
  const GRAUS_COEF = [
@@ -6132,7 +6133,12 @@ export default function ElaboracaoTab({ sessionId, authUser }) {
6132
  <tr key={`tr-knn-elab-${idxLinha}`}>
6133
  {knnDetalheTabela.columns.map((coluna) => (
6134
  <td key={`td-knn-elab-${idxLinha}-${coluna}`}>
6135
- {formatarValorTabelaKnn(coluna, linha?.[coluna])}
 
 
 
 
 
6136
  </td>
6137
  ))}
6138
  </tr>
 
9
  import PlotFigure from './PlotFigure'
10
  import SectionBlock from './SectionBlock'
11
  import SinglePillAutocomplete from './SinglePillAutocomplete'
12
+ import TruncatedCellContent from './TruncatedCellContent'
13
 
14
  const OPERADORES = ['<=', '>=', '<', '>', '=']
15
  const GRAUS_COEF = [
 
6133
  <tr key={`tr-knn-elab-${idxLinha}`}>
6134
  {knnDetalheTabela.columns.map((coluna) => (
6135
  <td key={`td-knn-elab-${idxLinha}-${coluna}`}>
6136
+ <TruncatedCellContent
6137
+ className="avaliacao-knn-table-cell"
6138
+ tooltipContent={String(formatarValorTabelaKnn(coluna, linha?.[coluna]))}
6139
+ >
6140
+ {formatarValorTabelaKnn(coluna, linha?.[coluna])}
6141
+ </TruncatedCellContent>
6142
  </td>
6143
  ))}
6144
  </tr>
frontend/src/components/RepositorioTab.jsx CHANGED
@@ -7,6 +7,7 @@ import LoadingOverlay from './LoadingOverlay'
7
  import MapFrame from './MapFrame'
8
  import ModeloTrabalhosTecnicosPanel from './ModeloTrabalhosTecnicosPanel'
9
  import PlotFigure from './PlotFigure'
 
10
  import { getFaixaDataRecencyInfo } from '../modelRecency'
11
 
12
  const PAGE_SIZE = 50
@@ -498,10 +499,10 @@ export default function RepositorioTab({ authUser, sessionId, openModeloRequest
498
  const periodoRecency = getFaixaDataRecencyInfo(item.periodo_dados)
499
  return (
500
  <tr key={key}>
501
- <td>{item.nome_modelo || item.arquivo || key}</td>
502
- <td>{item.tipo_imovel || '-'}</td>
503
- <td>{item.finalidade || '-'}</td>
504
- <td>{item.autor || '-'}</td>
505
  <td>
506
  <span className="repo-periodo-wrap">
507
  <span>{item.periodo_dados?.label || '-'}</span>
@@ -512,8 +513,8 @@ export default function RepositorioTab({ authUser, sessionId, openModeloRequest
512
  ) : null}
513
  </span>
514
  </td>
515
- <td>{item.total_dados ?? '-'}</td>
516
- <td>{item.total_trabalhos ?? '-'}</td>
517
  <td className="repo-col-open">
518
  <button
519
  type="button"
 
7
  import MapFrame from './MapFrame'
8
  import ModeloTrabalhosTecnicosPanel from './ModeloTrabalhosTecnicosPanel'
9
  import PlotFigure from './PlotFigure'
10
+ import TruncatedCellContent from './TruncatedCellContent'
11
  import { getFaixaDataRecencyInfo } from '../modelRecency'
12
 
13
  const PAGE_SIZE = 50
 
499
  const periodoRecency = getFaixaDataRecencyInfo(item.periodo_dados)
500
  return (
501
  <tr key={key}>
502
+ <td><TruncatedCellContent tooltipContent={item.nome_modelo || item.arquivo || key}>{item.nome_modelo || item.arquivo || key}</TruncatedCellContent></td>
503
+ <td><TruncatedCellContent tooltipContent={item.tipo_imovel || '-'}>{item.tipo_imovel || '-'}</TruncatedCellContent></td>
504
+ <td><TruncatedCellContent tooltipContent={item.finalidade || '-'}>{item.finalidade || '-'}</TruncatedCellContent></td>
505
+ <td><TruncatedCellContent tooltipContent={item.autor || '-'}>{item.autor || '-'}</TruncatedCellContent></td>
506
  <td>
507
  <span className="repo-periodo-wrap">
508
  <span>{item.periodo_dados?.label || '-'}</span>
 
513
  ) : null}
514
  </span>
515
  </td>
516
+ <td><TruncatedCellContent tooltipContent={String(item.total_dados ?? '-')}>{item.total_dados ?? '-'}</TruncatedCellContent></td>
517
+ <td><TruncatedCellContent tooltipContent={String(item.total_trabalhos ?? '-')}>{item.total_trabalhos ?? '-'}</TruncatedCellContent></td>
518
  <td className="repo-col-open">
519
  <button
520
  type="button"
frontend/src/components/TrabalhosTecnicosTab.jsx CHANGED
@@ -3,6 +3,7 @@ import { api } from '../api'
3
  import ListPagination from './ListPagination'
4
  import LoadingOverlay from './LoadingOverlay'
5
  import MapFrame from './MapFrame'
 
6
 
7
  const PAGE_SIZE = 50
8
  const TIPO_OPTIONS = ['LA', 'PT', 'IT', 'PTF', 'PIV']
@@ -1060,10 +1061,10 @@ export default function TrabalhosTecnicosTab({
1060
  <tbody>
1061
  {trabalhosPagina.map((item) => (
1062
  <tr key={String(item?.id || '')}>
1063
- <td className="trabalhos-col-nome">{item?.nome || '-'}</td>
1064
- <td className="trabalhos-col-tipo">{item?.tipo_label || item?.tipo_codigo || '-'}</td>
1065
- <td className="trabalhos-col-ano">{item?.ano || '-'}</td>
1066
- <td className="trabalhos-col-endereco">{item?.endereco_resumo || '-'}</td>
1067
  <td className="trabalhos-col-modelos">
1068
  {Array.isArray(item?.modelos) && item.modelos.length ? (
1069
  <div className="trabalho-model-stack">
@@ -1088,7 +1089,7 @@ export default function TrabalhosTecnicosTab({
1088
  ))}
1089
  </div>
1090
  ) : (
1091
- item?.modelo_resumo || '-'
1092
  )}
1093
  </td>
1094
  <td className="trabalhos-col-processos">
 
3
  import ListPagination from './ListPagination'
4
  import LoadingOverlay from './LoadingOverlay'
5
  import MapFrame from './MapFrame'
6
+ import TruncatedCellContent from './TruncatedCellContent'
7
 
8
  const PAGE_SIZE = 50
9
  const TIPO_OPTIONS = ['LA', 'PT', 'IT', 'PTF', 'PIV']
 
1061
  <tbody>
1062
  {trabalhosPagina.map((item) => (
1063
  <tr key={String(item?.id || '')}>
1064
+ <td className="trabalhos-col-nome"><TruncatedCellContent tooltipContent={item?.nome || '-'}>{item?.nome || '-'}</TruncatedCellContent></td>
1065
+ <td className="trabalhos-col-tipo"><TruncatedCellContent tooltipContent={item?.tipo_label || item?.tipo_codigo || '-'}>{item?.tipo_label || item?.tipo_codigo || '-'}</TruncatedCellContent></td>
1066
+ <td className="trabalhos-col-ano"><TruncatedCellContent tooltipContent={item?.ano || '-'}>{item?.ano || '-'}</TruncatedCellContent></td>
1067
+ <td className="trabalhos-col-endereco"><TruncatedCellContent tooltipContent={item?.endereco_resumo || '-'}>{item?.endereco_resumo || '-'}</TruncatedCellContent></td>
1068
  <td className="trabalhos-col-modelos">
1069
  {Array.isArray(item?.modelos) && item.modelos.length ? (
1070
  <div className="trabalho-model-stack">
 
1089
  ))}
1090
  </div>
1091
  ) : (
1092
+ <TruncatedCellContent tooltipContent={item?.modelo_resumo || '-'}>{item?.modelo_resumo || '-'}</TruncatedCellContent>
1093
  )}
1094
  </td>
1095
  <td className="trabalhos-col-processos">
frontend/src/components/TruncatedCellContent.jsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+
3
+ function isContentTruncated(element) {
4
+ if (!element) return false
5
+ return (element.scrollHeight - element.clientHeight) > 1 || (element.scrollWidth - element.clientWidth) > 1
6
+ }
7
+
8
+ export default function TruncatedCellContent({
9
+ children,
10
+ tooltipContent = '',
11
+ className = 'table-cell-clamp',
12
+ }) {
13
+ const contentRef = React.useRef(null)
14
+ const tooltipText = String(tooltipContent ?? '').trim()
15
+
16
+ const syncTooltip = React.useCallback(() => {
17
+ const element = contentRef.current
18
+ if (!element) return
19
+ if (!tooltipText || !isContentTruncated(element)) {
20
+ element.removeAttribute('title')
21
+ return
22
+ }
23
+ element.setAttribute('title', tooltipText)
24
+ }, [tooltipText])
25
+
26
+ const clearTooltip = React.useCallback(() => {
27
+ const element = contentRef.current
28
+ if (!element) return
29
+ element.removeAttribute('title')
30
+ }, [])
31
+
32
+ React.useEffect(() => {
33
+ const element = contentRef.current
34
+ if (!element) return undefined
35
+ element.removeAttribute('title')
36
+ return () => {
37
+ element.removeAttribute('title')
38
+ }
39
+ }, [tooltipText, children])
40
+
41
+ return (
42
+ <div
43
+ ref={contentRef}
44
+ className={className}
45
+ onMouseEnter={syncTooltip}
46
+ onFocus={syncTooltip}
47
+ onMouseLeave={clearTooltip}
48
+ onBlur={clearTooltip}
49
+ >
50
+ {children}
51
+ </div>
52
+ )
53
+ }
frontend/src/components/VisaoGeralTab.jsx CHANGED
@@ -1,6 +1,7 @@
1
  import React, { useEffect, useMemo, useRef, useState } from 'react'
2
  import { api } from '../api'
3
  import ListPagination from './ListPagination'
 
4
 
5
  const PAGE_SIZE = 50
6
  const SORT_COLLATOR = new Intl.Collator('pt-BR', { numeric: true, sensitivity: 'base' })
@@ -478,10 +479,12 @@ export default function VisaoGeralTab() {
478
  <tbody>
479
  {familiasPagina.map((item) => (
480
  <tr key={item.familia_modelos}>
481
- <td>{item.familia_modelos || '-'}</td>
482
- <td>{item.tipo_imovel || '-'}</td>
483
- <td>{item.finalidade || '-'}</td>
484
- <td className="repo-visao-geral-number-cell">{formatCount(item.total_avaliandos)}</td>
 
 
485
  </tr>
486
  ))}
487
  {!familiasFiltradasOrdenadas.length ? (
 
1
  import React, { useEffect, useMemo, useRef, useState } from 'react'
2
  import { api } from '../api'
3
  import ListPagination from './ListPagination'
4
+ import TruncatedCellContent from './TruncatedCellContent'
5
 
6
  const PAGE_SIZE = 50
7
  const SORT_COLLATOR = new Intl.Collator('pt-BR', { numeric: true, sensitivity: 'base' })
 
479
  <tbody>
480
  {familiasPagina.map((item) => (
481
  <tr key={item.familia_modelos}>
482
+ <td><TruncatedCellContent tooltipContent={item.familia_modelos || '-'}>{item.familia_modelos || '-'}</TruncatedCellContent></td>
483
+ <td><TruncatedCellContent tooltipContent={item.tipo_imovel || '-'}>{item.tipo_imovel || '-'}</TruncatedCellContent></td>
484
+ <td><TruncatedCellContent tooltipContent={item.finalidade || '-'}>{item.finalidade || '-'}</TruncatedCellContent></td>
485
+ <td className="repo-visao-geral-number-cell">
486
+ <TruncatedCellContent tooltipContent={formatCount(item.total_avaliandos)}>{formatCount(item.total_avaliandos)}</TruncatedCellContent>
487
+ </td>
488
  </tr>
489
  ))}
490
  {!familiasFiltradasOrdenadas.length ? (
frontend/src/styles.css CHANGED
@@ -4108,6 +4108,21 @@ button.pesquisa-coluna-remove:hover {
4108
  max-height: min(44vh, 420px);
4109
  }
4110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4111
  @media (max-width: 900px) {
4112
  .avaliacao-knn-map-wrap .map-frame {
4113
  min-height: 340px;
@@ -4802,8 +4817,8 @@ button.btn-upload-select {
4802
 
4803
  .table-wrapper table {
4804
  border-collapse: collapse;
4805
- min-width: 680px;
4806
- width: 100%;
4807
  }
4808
 
4809
  .table-wrapper th,
@@ -4812,6 +4827,7 @@ button.btn-upload-select {
4812
  border-bottom: 1px solid #edf2f6;
4813
  text-align: left;
4814
  font-size: 0.88rem;
 
4815
  }
4816
 
4817
  .table-wrapper th {
@@ -4886,6 +4902,24 @@ button.btn-upload-select {
4886
  background: #ffe882;
4887
  }
4888
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4889
  .table-hint {
4890
  border-top: 1px solid #edf2f6;
4891
  padding: 7px 9px;
 
4108
  max-height: min(44vh, 420px);
4109
  }
4110
 
4111
+ .avaliacao-knn-table-wrapper table {
4112
+ width: max-content;
4113
+ min-width: 100%;
4114
+ table-layout: fixed;
4115
+ }
4116
+
4117
+ .avaliacao-knn-table-wrapper th,
4118
+ .avaliacao-knn-table-wrapper td {
4119
+ vertical-align: top;
4120
+ }
4121
+
4122
+ .avaliacao-knn-table-cell {
4123
+ width: 100%;
4124
+ }
4125
+
4126
  @media (max-width: 900px) {
4127
  .avaliacao-knn-map-wrap .map-frame {
4128
  min-height: 340px;
 
4817
 
4818
  .table-wrapper table {
4819
  border-collapse: collapse;
4820
+ min-width: max(680px, 100%);
4821
+ width: max-content;
4822
  }
4823
 
4824
  .table-wrapper th,
 
4827
  border-bottom: 1px solid #edf2f6;
4828
  text-align: left;
4829
  font-size: 0.88rem;
4830
+ vertical-align: top;
4831
  }
4832
 
4833
  .table-wrapper th {
 
4902
  background: #ffe882;
4903
  }
4904
 
4905
+ .table-cell-clamp,
4906
+ .avaliacao-knn-table-cell {
4907
+ display: -webkit-box;
4908
+ -webkit-box-orient: vertical;
4909
+ -webkit-line-clamp: 3;
4910
+ overflow: hidden;
4911
+ white-space: normal;
4912
+ overflow-wrap: anywhere;
4913
+ word-break: break-word;
4914
+ line-height: 1.34;
4915
+ max-height: calc(1.34em * 3);
4916
+ max-width: 24ch;
4917
+ }
4918
+
4919
+ .avaliacao-knn-table-cell {
4920
+ max-width: none;
4921
+ }
4922
+
4923
  .table-hint {
4924
  border-top: 1px solid #edf2f6;
4925
  padding: 7px 9px;