Guilherme Silberfarb Costa commited on
Commit
5cc74bd
·
1 Parent(s): 949bf68

melhora no registro de variaveis selecionadas

Browse files
backend/app/core/pesquisa/pesquisa_admin_config.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "area": [
3
+ "var:ACONST",
4
+ "var:ALOC",
5
+ "var:APRIV",
6
+ "var:APRIVEQ",
7
+ "var:ATTOTAL"
8
+ ],
9
+ "aval_area": [
10
+ "var:ACONST",
11
+ "var:ALOC",
12
+ "var:APRIV",
13
+ "var:APRIVEQ",
14
+ "var:ATTOTAL"
15
+ ],
16
+ "aval_area_privativa": [
17
+ "var:APRIV",
18
+ "var:APRIVEQ"
19
+ ],
20
+ "aval_area_total": [
21
+ "var:ATTOTAL"
22
+ ],
23
+ "aval_bairro": [
24
+ "col:NME BAI"
25
+ ],
26
+ "aval_data": [],
27
+ "aval_finalidade": [
28
+ "col:FINALIDADE",
29
+ "col:NME IMO-FINAL"
30
+ ],
31
+ "aval_rh": [
32
+ "var:RH"
33
+ ],
34
+ "aval_valor_total": [
35
+ "var:VLOC",
36
+ "var:VTOTAL"
37
+ ],
38
+ "aval_valor_unitario": [
39
+ "var:VUACONST",
40
+ "var:VUNIPRIV",
41
+ "var:VUNIT"
42
+ ],
43
+ "bairros": [
44
+ "col:BAIRRO",
45
+ "col:Bairro",
46
+ "col:NME BAI"
47
+ ],
48
+ "data": [
49
+ "meta:faixa_data"
50
+ ],
51
+ "finalidade": [
52
+ "col:FINALIDADE",
53
+ "col:NME IMO-FINAL"
54
+ ],
55
+ "rh": [
56
+ "var:RH"
57
+ ]
58
+ }
frontend/src/components/ElaboracaoTab.jsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo, useRef, useState } from 'react'
2
  import { api, downloadBlob } from '../api'
3
  import Plotly from 'plotly.js-dist-min'
4
  import DataTable from './DataTable'
@@ -593,6 +593,7 @@ export default function ElaboracaoTab({ sessionId }) {
593
  const [section8Open, setSection8Open] = useState(false)
594
  const [section10ManualOpen, setSection10ManualOpen] = useState(false)
595
  const [section11Open, setSection11Open] = useState(false)
 
596
 
597
  const [fit, setFit] = useState(null)
598
  const [tipoDispersao, setTipoDispersao] = useState('Variáveis Independentes Transformadas X Variável Dependente Transformada')
@@ -624,6 +625,7 @@ export default function ElaboracaoTab({ sessionId }) {
624
  const classificarXReqRef = useRef(0)
625
  const deleteConfirmTimersRef = useRef({})
626
  const uploadInputRef = useRef(null)
 
627
 
628
  const mapaChoices = useMemo(() => ['Visualização Padrão', ...colunasNumericas], [colunasNumericas])
629
  const colunasXDisponiveis = useMemo(
@@ -668,6 +670,7 @@ export default function ElaboracaoTab({ sessionId }) {
668
  [periodoDadosMercadoPreview],
669
  )
670
  const pendingWarningText = 'Há alterações em campos que ainda não foram aplicadas.'
 
671
  const dataMercadoPendente = useMemo(
672
  () => String(colunaDataMercado || '') !== String(colunaDataMercadoAplicada || ''),
673
  [colunaDataMercado, colunaDataMercadoAplicada],
@@ -710,6 +713,34 @@ export default function ElaboracaoTab({ sessionId }) {
710
  () => Boolean(selection) && Boolean(section10ManualOpen) && manualTransformSnapshotAtual !== manualTransformAppliedSnapshot,
711
  [selection, section10ManualOpen, manualTransformSnapshotAtual, manualTransformAppliedSnapshot],
712
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  const outlierFiltrosSnapshotAtual = useMemo(
714
  () => buildFiltrosSnapshot(filtros),
715
  [filtros],
@@ -793,6 +824,59 @@ export default function ElaboracaoTab({ sessionId }) {
793
  )
794
  const baseCarregada = Boolean(dados)
795
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
  useEffect(() => {
797
  if (coordsInfo && !coordsInfo.tem_coords) {
798
  setCoordsMode('menu')
@@ -962,6 +1046,7 @@ export default function ElaboracaoTab({ sessionId }) {
962
  setPeriodoDadosMercadoPreview(null)
963
  setDataMercadoError('')
964
  setSelection(null)
 
965
  setFit(null)
966
  setFiltros(defaultFiltros())
967
  setOutliersTexto('')
@@ -998,6 +1083,7 @@ export default function ElaboracaoTab({ sessionId }) {
998
 
999
  function applySelectionResponse(resp) {
1000
  setSelection(resp)
 
1001
  setSection8Open(false)
1002
  setSection10ManualOpen(false)
1003
  setTransformacoesAplicadas(null)
@@ -1256,6 +1342,7 @@ export default function ElaboracaoTab({ sessionId }) {
1256
  setDicotomicas(proximasDicotomicas)
1257
  setCodigoAlocado(proximoCodigoAlocado)
1258
  setPercentuais(proximosPercentuais)
 
1259
  setSelectionAppliedSnapshot(buildSelectionSnapshot({ coluna_y: proximaColunaY }))
1260
  setSelection(null)
1261
  setFit(null)
@@ -1395,6 +1482,7 @@ export default function ElaboracaoTab({ sessionId }) {
1395
  const resp = await api.applySelection(payload)
1396
  applySelectionResponse(resp)
1397
  setSelectionAppliedSnapshot(buildSelectionSnapshot(payload))
 
1398
  setFit(null)
1399
  setOutlierFiltrosAplicadosSnapshot(buildFiltrosSnapshot(defaultFiltros()))
1400
  setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
@@ -1532,6 +1620,7 @@ export default function ElaboracaoTab({ sessionId }) {
1532
  setOutliersAnteriores([])
1533
  setIteracao(1)
1534
  setSelection(null)
 
1535
  setFit(null)
1536
  setTransformacoesAplicadas(null)
1537
  setOrigemTransformacoes(null)
@@ -2265,17 +2354,27 @@ export default function ElaboracaoTab({ sessionId }) {
2265
  </select>
2266
  </div>
2267
  <div className="row market-date-actions-row">
2268
- <div className="resumo-outliers-box">
2269
  Período identificado: {periodoDadosMercadoPreviewTexto}
2270
  </div>
2271
- <button
2272
- type="button"
2273
- onClick={onAplicarColunaDataMercado}
2274
- disabled={loading || dataMercadoLoading || !colunaDataMercado || !periodoDadosMercadoPreview}
2275
- >
2276
- Aplicar período
2277
- </button>
2278
- {dataMercadoPendente ? <span className="pending-apply-note">{pendingWarningText}</span> : null}
 
 
 
 
 
 
 
 
 
 
2279
  </div>
2280
  {dataMercadoError ? <div className="error-line inline-error">{dataMercadoError}</div> : null}
2281
  </div>
@@ -2290,15 +2389,25 @@ export default function ElaboracaoTab({ sessionId }) {
2290
  <option key={col} value={col}>{col}</option>
2291
  ))}
2292
  </select>
2293
- <button
2294
- type="button"
2295
- onClick={onAplicarVariavelDependente}
2296
- disabled={loading || !dependentePendente}
 
 
 
2297
  >
2298
- Aplicar variável dependente
2299
- </button>
 
 
 
 
 
 
2300
  {dependentePendente ? <span className="pending-apply-note">{pendingWarningText}</span> : null}
2301
  </div>
 
2302
  </SectionBlock>
2303
 
2304
  <SectionBlock step="6" title="Selecionar Variáveis Independentes" subtitle="Escolha regressoras e grupos de tipologia.">
@@ -2307,77 +2416,151 @@ export default function ElaboracaoTab({ sessionId }) {
2307
  Aplique a variável dependente na etapa anterior para liberar as opções de variáveis independentes.
2308
  </div>
2309
  ) : null}
2310
- <div className="compact-option-group compact-option-group-x">
2311
- <h4>Variáveis Independentes (X)</h4>
2312
- <div className="checkbox-inline-wrap checkbox-inline-wrap-tools">
2313
- <label className="compact-checkbox compact-checkbox-toggle-all">
2314
- <input
2315
- ref={marcarTodasXRef}
2316
- type="checkbox"
2317
- checked={todasXMarcadas}
2318
- onChange={onToggleTodasX}
2319
- disabled={colunasXDisponiveis.length === 0}
2320
- />
2321
- {todasXMarcadas ? 'Desmarcar todas' : 'Marcar todas'}
2322
- </label>
2323
- <span className="compact-selection-count">
2324
- {colunasX.length}/{colunasXDisponiveis.length} selecionadas
2325
- </span>
2326
- </div>
2327
- <div className="checkbox-inline-wrap">
2328
- {colunasXDisponiveis.map((col) => (
2329
- <label key={`x-${col}`} className="compact-checkbox">
2330
- <input
2331
- type="checkbox"
2332
- checked={colunasX.includes(col)}
2333
- onChange={() => onToggleColunaX(col)}
2334
- />
2335
- {col}
2336
- </label>
2337
- ))}
2338
  </div>
2339
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2340
 
2341
- <div className="compact-option-group compact-option-group-dicotomicas">
2342
- <h4>Variáveis Dicotômicas (0/1)</h4>
2343
- <div className="checkbox-inline-wrap">
2344
- {colunasX.map((col) => (
2345
- <label key={`d-${col}`} className="compact-checkbox">
2346
- <input type="checkbox" checked={dicotomicas.includes(col)} onChange={() => toggleSelection(setDicotomicas, col)} />
2347
- {col}
2348
- </label>
2349
- ))}
2350
- </div>
2351
- </div>
2352
 
2353
- <div className="compact-option-group compact-option-group-codigo">
2354
- <h4>Variáveis de Código Alocado/Ajustado</h4>
2355
- <div className="checkbox-inline-wrap">
2356
- {colunasX.map((col) => (
2357
- <label key={`c-${col}`} className="compact-checkbox">
2358
- <input type="checkbox" checked={codigoAlocado.includes(col)} onChange={() => toggleSelection(setCodigoAlocado, col)} />
2359
- {col}
2360
- </label>
2361
- ))}
2362
- </div>
2363
- </div>
2364
 
2365
- <div className="compact-option-group compact-option-group-percentuais">
2366
- <h4>Variáveis Percentuais (0 a 1)</h4>
2367
- <div className="checkbox-inline-wrap">
2368
- {colunasX.map((col) => (
2369
- <label key={`p-${col}`} className="compact-checkbox">
2370
- <input type="checkbox" checked={percentuais.includes(col)} onChange={() => toggleSelection(setPercentuais, col)} />
2371
- {col}
2372
- </label>
2373
- ))}
2374
- </div>
2375
- </div>
2376
 
2377
- <div className="row">
2378
- <button onClick={onApplySelection} disabled={loading || dependentePendente || !colunaY || colunasX.length === 0}>Aplicar seleção</button>
2379
- {selecaoPendente ? <span className="pending-apply-note">{pendingWarningText}</span> : null}
2380
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2381
  {selection?.aviso_multicolinearidade?.visible ? (
2382
  <div dangerouslySetInnerHTML={{ __html: selection.aviso_multicolinearidade.html }} />
2383
  ) : null}
@@ -2571,7 +2754,14 @@ export default function ElaboracaoTab({ sessionId }) {
2571
  </div>
2572
 
2573
  <div className="row row-fit-transformacoes">
2574
- <button className="btn-fit-model" onClick={onFitModel} disabled={loading}>Aplicar transformações e ajustar modelo</button>
 
 
 
 
 
 
 
2575
  {manualTransformPendente ? <span className="pending-apply-note">{pendingWarningText}</span> : null}
2576
  </div>
2577
  </>
@@ -2980,6 +3170,14 @@ export default function ElaboracaoTab({ sessionId }) {
2980
  </>
2981
  ) : null}
2982
 
 
 
 
 
 
 
 
 
2983
  <LoadingOverlay show={loading} label="Processando dados..." />
2984
  {error ? <div className="error-line">{error}</div> : null}
2985
  </div>
 
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
  import { api, downloadBlob } from '../api'
3
  import Plotly from 'plotly.js-dist-min'
4
  import DataTable from './DataTable'
 
593
  const [section8Open, setSection8Open] = useState(false)
594
  const [section10ManualOpen, setSection10ManualOpen] = useState(false)
595
  const [section11Open, setSection11Open] = useState(false)
596
+ const [section6EditOpen, setSection6EditOpen] = useState(true)
597
 
598
  const [fit, setFit] = useState(null)
599
  const [tipoDispersao, setTipoDispersao] = useState('Variáveis Independentes Transformadas X Variável Dependente Transformada')
 
625
  const classificarXReqRef = useRef(0)
626
  const deleteConfirmTimersRef = useRef({})
627
  const uploadInputRef = useRef(null)
628
+ const [disabledHint, setDisabledHint] = useState(null)
629
 
630
  const mapaChoices = useMemo(() => ['Visualização Padrão', ...colunasNumericas], [colunasNumericas])
631
  const colunasXDisponiveis = useMemo(
 
670
  [periodoDadosMercadoPreview],
671
  )
672
  const pendingWarningText = 'Há alterações em campos que ainda não foram aplicadas.'
673
+ const semAlteracaoTooltipText = 'Não houve modificação para ser aplicada.'
674
  const dataMercadoPendente = useMemo(
675
  () => String(colunaDataMercado || '') !== String(colunaDataMercadoAplicada || ''),
676
  [colunaDataMercado, colunaDataMercadoAplicada],
 
713
  () => Boolean(selection) && Boolean(section10ManualOpen) && manualTransformSnapshotAtual !== manualTransformAppliedSnapshot,
714
  [selection, section10ManualOpen, manualTransformSnapshotAtual, manualTransformAppliedSnapshot],
715
  )
716
+ const podeAplicarDataMercado = useMemo(
717
+ () => dataMercadoPendente && Boolean(colunaDataMercado) && Boolean(periodoDadosMercadoPreview),
718
+ [dataMercadoPendente, colunaDataMercado, periodoDadosMercadoPreview],
719
+ )
720
+ const semAlteracaoDataMercado = useMemo(
721
+ () => !dataMercadoPendente && Boolean(colunaDataMercadoAplicada),
722
+ [dataMercadoPendente, colunaDataMercadoAplicada],
723
+ )
724
+ const semAlteracaoDependente = useMemo(
725
+ () => !dependentePendente && Boolean(colunaYDraft),
726
+ [dependentePendente, colunaYDraft],
727
+ )
728
+ const podeAplicarSelecao = useMemo(
729
+ () => selecaoPendente && !dependentePendente && Boolean(colunaY) && colunasX.length > 0,
730
+ [selecaoPendente, dependentePendente, colunaY, colunasX],
731
+ )
732
+ const semAlteracaoSelecao = useMemo(
733
+ () => !selecaoPendente && !dependentePendente && Boolean(colunaY) && colunasX.length > 0,
734
+ [selecaoPendente, dependentePendente, colunaY, colunasX],
735
+ )
736
+ const podeAplicarTransformacaoManual = useMemo(
737
+ () => manualTransformPendente,
738
+ [manualTransformPendente],
739
+ )
740
+ const semAlteracaoTransformacaoManual = useMemo(
741
+ () => !manualTransformPendente && Boolean(selection) && Boolean(section10ManualOpen),
742
+ [manualTransformPendente, selection, section10ManualOpen],
743
+ )
744
  const outlierFiltrosSnapshotAtual = useMemo(
745
  () => buildFiltrosSnapshot(filtros),
746
  [filtros],
 
824
  )
825
  const baseCarregada = Boolean(dados)
826
 
827
+ const hideDisabledHint = useCallback(() => {
828
+ setDisabledHint(null)
829
+ }, [])
830
+
831
+ const onDisabledHintEnter = useCallback((event, showHint, hintText) => {
832
+ if (!showHint || !hintText || typeof window === 'undefined') {
833
+ setDisabledHint(null)
834
+ return
835
+ }
836
+
837
+ const anchor = event.currentTarget
838
+ if (!anchor || typeof anchor.getBoundingClientRect !== 'function') {
839
+ setDisabledHint(null)
840
+ return
841
+ }
842
+
843
+ const anchorRect = anchor.getBoundingClientRect()
844
+ const sectionEl = anchor.closest('.workflow-section')
845
+ const sectionRect = sectionEl?.getBoundingClientRect?.()
846
+ const containerRect = sectionRect || {
847
+ top: 0,
848
+ left: 0,
849
+ right: window.innerWidth,
850
+ bottom: window.innerHeight,
851
+ }
852
+
853
+ const tooltipWidth = 280
854
+ const tooltipHeight = 44
855
+ const gap = 10
856
+ const margin = 12
857
+
858
+ const distLeft = anchorRect.left - containerRect.left
859
+ const distRight = containerRect.right - anchorRect.right
860
+ const distTop = anchorRect.top - containerRect.top
861
+ const distBottom = containerRect.bottom - anchorRect.bottom
862
+
863
+ const openToRight = distLeft <= distRight
864
+ const openToTop = distBottom <= distTop
865
+
866
+ let left = openToRight ? anchorRect.left : anchorRect.right - tooltipWidth
867
+ let top = openToTop ? anchorRect.top - tooltipHeight - gap : anchorRect.bottom + gap
868
+
869
+ left = Math.max(margin, Math.min(left, window.innerWidth - tooltipWidth - margin))
870
+ top = Math.max(margin, Math.min(top, window.innerHeight - tooltipHeight - margin))
871
+
872
+ setDisabledHint({
873
+ text: String(hintText),
874
+ left,
875
+ top,
876
+ width: tooltipWidth,
877
+ })
878
+ }, [])
879
+
880
  useEffect(() => {
881
  if (coordsInfo && !coordsInfo.tem_coords) {
882
  setCoordsMode('menu')
 
1046
  setPeriodoDadosMercadoPreview(null)
1047
  setDataMercadoError('')
1048
  setSelection(null)
1049
+ setSection6EditOpen(true)
1050
  setFit(null)
1051
  setFiltros(defaultFiltros())
1052
  setOutliersTexto('')
 
1083
 
1084
  function applySelectionResponse(resp) {
1085
  setSelection(resp)
1086
+ setSection6EditOpen(false)
1087
  setSection8Open(false)
1088
  setSection10ManualOpen(false)
1089
  setTransformacoesAplicadas(null)
 
1342
  setDicotomicas(proximasDicotomicas)
1343
  setCodigoAlocado(proximoCodigoAlocado)
1344
  setPercentuais(proximosPercentuais)
1345
+ setSection6EditOpen(true)
1346
  setSelectionAppliedSnapshot(buildSelectionSnapshot({ coluna_y: proximaColunaY }))
1347
  setSelection(null)
1348
  setFit(null)
 
1482
  const resp = await api.applySelection(payload)
1483
  applySelectionResponse(resp)
1484
  setSelectionAppliedSnapshot(buildSelectionSnapshot(payload))
1485
+ setSection6EditOpen(false)
1486
  setFit(null)
1487
  setOutlierFiltrosAplicadosSnapshot(buildFiltrosSnapshot(defaultFiltros()))
1488
  setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
 
1620
  setOutliersAnteriores([])
1621
  setIteracao(1)
1622
  setSelection(null)
1623
+ setSection6EditOpen(true)
1624
  setFit(null)
1625
  setTransformacoesAplicadas(null)
1626
  setOrigemTransformacoes(null)
 
2354
  </select>
2355
  </div>
2356
  <div className="row market-date-actions-row">
2357
+ <div className="resumo-outliers-box market-date-period-row">
2358
  Período identificado: {periodoDadosMercadoPreviewTexto}
2359
  </div>
2360
+ <div className="row market-date-apply-row">
2361
+ <span
2362
+ className={`button-tooltip-wrap${semAlteracaoDataMercado ? ' is-disabled-hint' : ''}`}
2363
+ title={semAlteracaoDataMercado ? semAlteracaoTooltipText : ''}
2364
+ onMouseEnter={(event) => onDisabledHintEnter(event, semAlteracaoDataMercado, semAlteracaoTooltipText)}
2365
+ onMouseLeave={hideDisabledHint}
2366
+ >
2367
+ <button
2368
+ type="button"
2369
+ onClick={onAplicarColunaDataMercado}
2370
+ disabled={loading || dataMercadoLoading || !podeAplicarDataMercado}
2371
+ >
2372
+ Aplicar
2373
+ </button>
2374
+ </span>
2375
+ {dataMercadoPendente ? <span className="pending-apply-note">{pendingWarningText}</span> : null}
2376
+ </div>
2377
+ <div className="section1-empty-hint">Selecionado: {colunaDataMercado || '-'}</div>
2378
  </div>
2379
  {dataMercadoError ? <div className="error-line inline-error">{dataMercadoError}</div> : null}
2380
  </div>
 
2389
  <option key={col} value={col}>{col}</option>
2390
  ))}
2391
  </select>
2392
+ </div>
2393
+ <div className="row section5-apply-row">
2394
+ <span
2395
+ className={`button-tooltip-wrap${semAlteracaoDependente ? ' is-disabled-hint' : ''}`}
2396
+ title={semAlteracaoDependente ? semAlteracaoTooltipText : ''}
2397
+ onMouseEnter={(event) => onDisabledHintEnter(event, semAlteracaoDependente, semAlteracaoTooltipText)}
2398
+ onMouseLeave={hideDisabledHint}
2399
  >
2400
+ <button
2401
+ type="button"
2402
+ onClick={onAplicarVariavelDependente}
2403
+ disabled={loading || !dependentePendente}
2404
+ >
2405
+ Aplicar
2406
+ </button>
2407
+ </span>
2408
  {dependentePendente ? <span className="pending-apply-note">{pendingWarningText}</span> : null}
2409
  </div>
2410
+ <div className="section1-empty-hint">Selecionado: {colunaYDraft || '-'}</div>
2411
  </SectionBlock>
2412
 
2413
  <SectionBlock step="6" title="Selecionar Variáveis Independentes" subtitle="Escolha regressoras e grupos de tipologia.">
 
2416
  Aplique a variável dependente na etapa anterior para liberar as opções de variáveis independentes.
2417
  </div>
2418
  ) : null}
2419
+ {colunaY ? (
2420
+ <div className="manual-transform-toggle section6-toggle-wrap">
2421
+ <button
2422
+ type="button"
2423
+ className={section6EditOpen ? 'btn-manual-toggle active' : 'btn-manual-toggle'}
2424
+ onClick={() => setSection6EditOpen((prev) => !prev)}
2425
+ >
2426
+ {section6EditOpen ? 'Ocultar edição de variáveis independentes' : 'Alterar variáveis independentes'}
2427
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2428
  </div>
2429
+ ) : null}
2430
+ {colunaY && section6EditOpen ? (
2431
+ <>
2432
+ <div className="compact-option-group compact-option-group-x">
2433
+ <h4>Variáveis Independentes (X)</h4>
2434
+ <div className="checkbox-inline-wrap checkbox-inline-wrap-tools">
2435
+ <label className="compact-checkbox compact-checkbox-toggle-all">
2436
+ <input
2437
+ ref={marcarTodasXRef}
2438
+ type="checkbox"
2439
+ checked={todasXMarcadas}
2440
+ onChange={onToggleTodasX}
2441
+ disabled={colunasXDisponiveis.length === 0}
2442
+ />
2443
+ {todasXMarcadas ? 'Desmarcar todas' : 'Marcar todas'}
2444
+ </label>
2445
+ <span className="compact-selection-count">
2446
+ {colunasX.length}/{colunasXDisponiveis.length} selecionadas
2447
+ </span>
2448
+ </div>
2449
+ <div className="checkbox-inline-wrap">
2450
+ {colunasXDisponiveis.map((col) => (
2451
+ <label key={`x-${col}`} className="compact-checkbox">
2452
+ <input
2453
+ type="checkbox"
2454
+ checked={colunasX.includes(col)}
2455
+ onChange={() => onToggleColunaX(col)}
2456
+ />
2457
+ {col}
2458
+ </label>
2459
+ ))}
2460
+ </div>
2461
+ </div>
2462
 
2463
+ <div className="compact-option-group compact-option-group-dicotomicas">
2464
+ <h4>Variáveis Dicotômicas (0/1)</h4>
2465
+ <div className="checkbox-inline-wrap">
2466
+ {colunasX.map((col) => (
2467
+ <label key={`d-${col}`} className="compact-checkbox">
2468
+ <input type="checkbox" checked={dicotomicas.includes(col)} onChange={() => toggleSelection(setDicotomicas, col)} />
2469
+ {col}
2470
+ </label>
2471
+ ))}
2472
+ </div>
2473
+ </div>
2474
 
2475
+ <div className="compact-option-group compact-option-group-codigo">
2476
+ <h4>Variáveis de Código Alocado/Ajustado</h4>
2477
+ <div className="checkbox-inline-wrap">
2478
+ {colunasX.map((col) => (
2479
+ <label key={`c-${col}`} className="compact-checkbox">
2480
+ <input type="checkbox" checked={codigoAlocado.includes(col)} onChange={() => toggleSelection(setCodigoAlocado, col)} />
2481
+ {col}
2482
+ </label>
2483
+ ))}
2484
+ </div>
2485
+ </div>
2486
 
2487
+ <div className="compact-option-group compact-option-group-percentuais">
2488
+ <h4>Variáveis Percentuais (0 a 1)</h4>
2489
+ <div className="checkbox-inline-wrap">
2490
+ {colunasX.map((col) => (
2491
+ <label key={`p-${col}`} className="compact-checkbox">
2492
+ <input type="checkbox" checked={percentuais.includes(col)} onChange={() => toggleSelection(setPercentuais, col)} />
2493
+ {col}
2494
+ </label>
2495
+ ))}
2496
+ </div>
2497
+ </div>
2498
 
2499
+ <div className="row">
2500
+ <span
2501
+ className={`button-tooltip-wrap${semAlteracaoSelecao ? ' is-disabled-hint' : ''}`}
2502
+ title={semAlteracaoSelecao ? semAlteracaoTooltipText : ''}
2503
+ onMouseEnter={(event) => onDisabledHintEnter(event, semAlteracaoSelecao, semAlteracaoTooltipText)}
2504
+ onMouseLeave={hideDisabledHint}
2505
+ >
2506
+ <button onClick={onApplySelection} disabled={loading || !podeAplicarSelecao}>Aplicar seleção</button>
2507
+ </span>
2508
+ {selecaoPendente ? <span className="pending-apply-note">{pendingWarningText}</span> : null}
2509
+ </div>
2510
+ </>
2511
+ ) : null}
2512
+ {colunaY ? (
2513
+ <div className="section6-selected-summary">
2514
+ <div className="section6-summary-group">
2515
+ <div className="section6-summary-label">Independentes:</div>
2516
+ {colunasX.length > 0 ? (
2517
+ <div className="checkbox-inline-wrap">
2518
+ {colunasX.map((coluna) => (
2519
+ <span key={`selected-x-${coluna}`} className="compact-chip">{coluna}</span>
2520
+ ))}
2521
+ </div>
2522
+ ) : (
2523
+ <div className="section1-empty-hint">Nenhuma.</div>
2524
+ )}
2525
+ </div>
2526
+ <div className="section6-summary-group">
2527
+ <div className="section6-summary-label">Dicotômicas:</div>
2528
+ {dicotomicas.length > 0 ? (
2529
+ <div className="checkbox-inline-wrap">
2530
+ {dicotomicas.map((coluna) => (
2531
+ <span key={`selected-d-${coluna}`} className="compact-chip">{coluna}</span>
2532
+ ))}
2533
+ </div>
2534
+ ) : (
2535
+ <div className="section1-empty-hint">Nenhuma.</div>
2536
+ )}
2537
+ </div>
2538
+ <div className="section6-summary-group">
2539
+ <div className="section6-summary-label">Código alocado/ajustado:</div>
2540
+ {codigoAlocado.length > 0 ? (
2541
+ <div className="checkbox-inline-wrap">
2542
+ {codigoAlocado.map((coluna) => (
2543
+ <span key={`selected-c-${coluna}`} className="compact-chip">{coluna}</span>
2544
+ ))}
2545
+ </div>
2546
+ ) : (
2547
+ <div className="section1-empty-hint">Nenhuma.</div>
2548
+ )}
2549
+ </div>
2550
+ <div className="section6-summary-group">
2551
+ <div className="section6-summary-label">Percentuais:</div>
2552
+ {percentuais.length > 0 ? (
2553
+ <div className="checkbox-inline-wrap">
2554
+ {percentuais.map((coluna) => (
2555
+ <span key={`selected-p-${coluna}`} className="compact-chip">{coluna}</span>
2556
+ ))}
2557
+ </div>
2558
+ ) : (
2559
+ <div className="section1-empty-hint">Nenhuma.</div>
2560
+ )}
2561
+ </div>
2562
+ </div>
2563
+ ) : null}
2564
  {selection?.aviso_multicolinearidade?.visible ? (
2565
  <div dangerouslySetInnerHTML={{ __html: selection.aviso_multicolinearidade.html }} />
2566
  ) : null}
 
2754
  </div>
2755
 
2756
  <div className="row row-fit-transformacoes">
2757
+ <span
2758
+ className={`button-tooltip-wrap${semAlteracaoTransformacaoManual ? ' is-disabled-hint' : ''}`}
2759
+ title={semAlteracaoTransformacaoManual ? semAlteracaoTooltipText : ''}
2760
+ onMouseEnter={(event) => onDisabledHintEnter(event, semAlteracaoTransformacaoManual, semAlteracaoTooltipText)}
2761
+ onMouseLeave={hideDisabledHint}
2762
+ >
2763
+ <button className="btn-fit-model" onClick={onFitModel} disabled={loading || !podeAplicarTransformacaoManual}>Aplicar transformações e ajustar modelo</button>
2764
+ </span>
2765
  {manualTransformPendente ? <span className="pending-apply-note">{pendingWarningText}</span> : null}
2766
  </div>
2767
  </>
 
3170
  </>
3171
  ) : null}
3172
 
3173
+ {disabledHint ? (
3174
+ <div
3175
+ className="disabled-change-tooltip"
3176
+ style={{ left: `${disabledHint.left}px`, top: `${disabledHint.top}px`, width: `${disabledHint.width}px` }}
3177
+ >
3178
+ {disabledHint.text}
3179
+ </div>
3180
+ ) : null}
3181
  <LoadingOverlay show={loading} label="Processando dados..." />
3182
  {error ? <div className="error-line">{error}</div> : null}
3183
  </div>
frontend/src/styles.css CHANGED
@@ -1860,6 +1860,10 @@ button.btn-upload-select {
1860
  margin: 8px 0 10px;
1861
  }
1862
 
 
 
 
 
1863
  .btn-manual-toggle {
1864
  min-width: 320px;
1865
  }
@@ -1872,6 +1876,64 @@ button.btn-upload-select {
1872
  --btn-shadow-strong: rgba(86, 105, 122, 0.24);
1873
  }
1874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1875
  .transform-suggestion-item {
1876
  display: grid;
1877
  grid-template-columns: minmax(0, 1fr) auto auto;
@@ -2328,19 +2390,25 @@ button.btn-upload-select {
2328
  }
2329
 
2330
  .market-date-actions-row {
2331
- align-items: center;
2332
- justify-content: space-between;
2333
  gap: 10px;
2334
- flex-wrap: wrap;
 
2335
  }
2336
 
2337
- .market-date-actions-row .resumo-outliers-box {
2338
  margin-top: 0;
2339
  min-height: 38px;
2340
  display: inline-flex;
2341
  align-items: center;
2342
  }
2343
 
 
 
 
 
 
2344
  .pending-apply-note {
2345
  display: inline-flex;
2346
  align-items: center;
 
1860
  margin: 8px 0 10px;
1861
  }
1862
 
1863
+ .section6-toggle-wrap {
1864
+ margin-bottom: 12px;
1865
+ }
1866
+
1867
  .btn-manual-toggle {
1868
  min-width: 320px;
1869
  }
 
1876
  --btn-shadow-strong: rgba(86, 105, 122, 0.24);
1877
  }
1878
 
1879
+ .section6-selected-summary {
1880
+ margin-top: 8px;
1881
+ display: grid;
1882
+ gap: 8px;
1883
+ }
1884
+
1885
+ .section6-summary-group {
1886
+ display: grid;
1887
+ gap: 5px;
1888
+ }
1889
+
1890
+ .section6-summary-label {
1891
+ font-size: 0.79rem;
1892
+ font-weight: 700;
1893
+ color: #4d647b;
1894
+ }
1895
+
1896
+ .compact-chip {
1897
+ display: inline-flex;
1898
+ align-items: center;
1899
+ gap: 5px;
1900
+ padding: 4px 7px;
1901
+ border: 1px solid #dfe8f1;
1902
+ border-radius: 8px;
1903
+ background: #fbfdff;
1904
+ font-size: 0.83rem;
1905
+ font-weight: 600;
1906
+ color: #3a4f64;
1907
+ line-height: 1.15;
1908
+ }
1909
+
1910
+ .button-tooltip-wrap {
1911
+ position: relative;
1912
+ display: inline-flex;
1913
+ }
1914
+
1915
+ .button-tooltip-wrap.is-disabled-hint {
1916
+ cursor: not-allowed;
1917
+ }
1918
+
1919
+ .button-tooltip-wrap.is-disabled-hint > button:disabled {
1920
+ pointer-events: none;
1921
+ }
1922
+
1923
+ .disabled-change-tooltip {
1924
+ position: fixed;
1925
+ z-index: 3500;
1926
+ pointer-events: none;
1927
+ padding: 6px 10px;
1928
+ border-radius: 8px;
1929
+ background: #3f5368;
1930
+ color: #fff;
1931
+ font-size: 0.76rem;
1932
+ line-height: 1.25;
1933
+ text-align: left;
1934
+ box-shadow: 0 8px 18px rgba(28, 46, 66, 0.26);
1935
+ }
1936
+
1937
  .transform-suggestion-item {
1938
  display: grid;
1939
  grid-template-columns: minmax(0, 1fr) auto auto;
 
2390
  }
2391
 
2392
  .market-date-actions-row {
2393
+ align-items: flex-start;
2394
+ justify-content: flex-start;
2395
  gap: 10px;
2396
+ flex-wrap: nowrap;
2397
+ flex-direction: column;
2398
  }
2399
 
2400
+ .market-date-period-row {
2401
  margin-top: 0;
2402
  min-height: 38px;
2403
  display: inline-flex;
2404
  align-items: center;
2405
  }
2406
 
2407
+ .market-date-apply-row,
2408
+ .section5-apply-row {
2409
+ margin-bottom: 0;
2410
+ }
2411
+
2412
  .pending-apply-note {
2413
  display: inline-flex;
2414
  align-items: center;