Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
275fd5b
1
Parent(s): e018b1c
inclusao do botao salvar no repositorio e do campo de obsercaoes
Browse files- backend/app/api/elaboracao.py +38 -1
- backend/app/core/elaboracao/carregamento.py +1 -0
- backend/app/core/elaboracao/core.py +14 -2
- backend/app/core/visualizacao/app.py +9 -4
- backend/app/models/session.py +2 -0
- backend/app/services/elaboracao_service.py +56 -1
- backend/app/services/visualizacao_service.py +3 -0
- frontend/src/App.jsx +1 -1
- frontend/src/api.js +14 -2
- frontend/src/components/AvaliacaoTab.jsx +23 -2
- frontend/src/components/ElaboracaoTab.jsx +180 -21
- frontend/src/components/PesquisaTab.jsx +2 -1
- frontend/src/components/RepositorioTab.jsx +2 -1
- frontend/src/styles.css +149 -0
backend/app/api/elaboracao.py
CHANGED
|
@@ -144,6 +144,11 @@ class AvaliacaoBasePayload(SessionPayload):
|
|
| 144 |
class ExportModeloPayload(SessionPayload):
|
| 145 |
nome_arquivo: str
|
| 146 |
elaborador: dict[str, Any] | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
|
| 149 |
class UpdateMapaPayload(SessionPayload):
|
|
@@ -455,7 +460,12 @@ def listar_avaliadores() -> dict[str, Any]:
|
|
| 455 |
@router.post("/export-model")
|
| 456 |
def export_model(payload: ExportModeloPayload, request: Request) -> FileResponse:
|
| 457 |
session = session_store.get(payload.session_id)
|
| 458 |
-
caminho, _ = elaboracao_service.exportar_modelo(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
user = auth_service.require_user(request)
|
| 460 |
log_event(
|
| 461 |
"elaboracao",
|
|
@@ -472,6 +482,33 @@ def export_model(payload: ExportModeloPayload, request: Request) -> FileResponse
|
|
| 472 |
)
|
| 473 |
|
| 474 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
@router.get("/export-base")
|
| 476 |
def export_base(session_id: str, filtered: bool = True) -> FileResponse:
|
| 477 |
session = session_store.get(session_id)
|
|
|
|
| 144 |
class ExportModeloPayload(SessionPayload):
|
| 145 |
nome_arquivo: str
|
| 146 |
elaborador: dict[str, Any] | None = None
|
| 147 |
+
observacao_modelo: str | None = None
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
class SalvarModeloRepositorioPayload(ExportModeloPayload):
|
| 151 |
+
confirmar_substituicao: bool = False
|
| 152 |
|
| 153 |
|
| 154 |
class UpdateMapaPayload(SessionPayload):
|
|
|
|
| 460 |
@router.post("/export-model")
|
| 461 |
def export_model(payload: ExportModeloPayload, request: Request) -> FileResponse:
|
| 462 |
session = session_store.get(payload.session_id)
|
| 463 |
+
caminho, _ = elaboracao_service.exportar_modelo(
|
| 464 |
+
session,
|
| 465 |
+
payload.nome_arquivo,
|
| 466 |
+
elaborador=payload.elaborador,
|
| 467 |
+
observacao_modelo=payload.observacao_modelo,
|
| 468 |
+
)
|
| 469 |
user = auth_service.require_user(request)
|
| 470 |
log_event(
|
| 471 |
"elaboracao",
|
|
|
|
| 482 |
)
|
| 483 |
|
| 484 |
|
| 485 |
+
@router.post("/save-model-repository")
|
| 486 |
+
def save_model_repository(payload: SalvarModeloRepositorioPayload, request: Request) -> dict[str, Any]:
|
| 487 |
+
session = session_store.get(payload.session_id)
|
| 488 |
+
user = auth_service.require_admin(request)
|
| 489 |
+
resposta = elaboracao_service.salvar_modelo_repositorio(
|
| 490 |
+
session,
|
| 491 |
+
payload.nome_arquivo,
|
| 492 |
+
elaborador=payload.elaborador,
|
| 493 |
+
observacao_modelo=payload.observacao_modelo,
|
| 494 |
+
actor=user.get("usuario"),
|
| 495 |
+
confirmar_substituicao=payload.confirmar_substituicao,
|
| 496 |
+
)
|
| 497 |
+
log_event(
|
| 498 |
+
"repositorio",
|
| 499 |
+
"salvar_modelo_repositorio_elaboracao",
|
| 500 |
+
user=user,
|
| 501 |
+
session_id=payload.session_id,
|
| 502 |
+
request=request,
|
| 503 |
+
details={
|
| 504 |
+
"nome_arquivo": payload.nome_arquivo,
|
| 505 |
+
"confirmar_substituicao": bool(payload.confirmar_substituicao),
|
| 506 |
+
"substituidos": resposta.get("resultado_upload", {}).get("substituidos", []),
|
| 507 |
+
},
|
| 508 |
+
)
|
| 509 |
+
return resposta
|
| 510 |
+
|
| 511 |
+
|
| 512 |
@router.get("/export-base")
|
| 513 |
def export_base(session_id: str, filtered: bool = True) -> FileResponse:
|
| 514 |
session = session_store.get(session_id)
|
backend/app/core/elaboracao/carregamento.py
CHANGED
|
@@ -270,6 +270,7 @@ def carregar_dados_de_dai(caminho_arquivo):
|
|
| 270 |
msg,
|
| 271 |
sucesso,
|
| 272 |
elaborador,
|
|
|
|
| 273 |
outliers_excluidos,
|
| 274 |
_periodo_dados_mercado,
|
| 275 |
_config_avaliacao,
|
|
|
|
| 270 |
msg,
|
| 271 |
sucesso,
|
| 272 |
elaborador,
|
| 273 |
+
_observacao_modelo,
|
| 274 |
outliers_excluidos,
|
| 275 |
_periodo_dados_mercado,
|
| 276 |
_config_avaliacao,
|
backend/app/core/elaboracao/core.py
CHANGED
|
@@ -376,12 +376,18 @@ def normalizar_config_avaliacao_modelo(config, df=None):
|
|
| 376 |
}
|
| 377 |
|
| 378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
def carregar_dai(caminho):
|
| 380 |
"""
|
| 381 |
Carrega arquivo .dai e extrai DataFrame, variáveis e transformações.
|
| 382 |
|
| 383 |
Retorna:
|
| 384 |
-
tuple: (df, coluna_y, colunas_x, transformacao_y, transformacoes_x, dicotomicas, codigo_alocado, percentuais, mensagem, sucesso, elaborador, outliers_excluidos, periodo_dados_mercado, config_avaliacao)
|
| 385 |
"""
|
| 386 |
try:
|
| 387 |
pacote = load(caminho)
|
|
@@ -458,6 +464,7 @@ def carregar_dai(caminho):
|
|
| 458 |
df=df,
|
| 459 |
)
|
| 460 |
elaborador = pacote.get("elaborador", None)
|
|
|
|
| 461 |
msg = f"Modelo .dai carregado: {os.path.basename(caminho)} ({len(df)} dados, Y={coluna_y}, {len(colunas_x)} variáveis X)"
|
| 462 |
return (
|
| 463 |
df,
|
|
@@ -471,6 +478,7 @@ def carregar_dai(caminho):
|
|
| 471 |
msg,
|
| 472 |
True,
|
| 473 |
elaborador,
|
|
|
|
| 474 |
outliers_excluidos,
|
| 475 |
periodo_dados_mercado,
|
| 476 |
config_avaliacao,
|
|
@@ -489,6 +497,7 @@ def carregar_dai(caminho):
|
|
| 489 |
f"Erro ao carregar .dai: {str(e)}",
|
| 490 |
False,
|
| 491 |
None,
|
|
|
|
| 492 |
[],
|
| 493 |
{"data_inicial": None, "data_final": None},
|
| 494 |
{"tipo_y": None, "coluna_area": None},
|
|
@@ -2026,7 +2035,8 @@ def exportar_base_csv(df):
|
|
| 2026 |
|
| 2027 |
def exportar_modelo_dai(resultado_modelo, df_original, df_completo=None, estatisticas=None,
|
| 2028 |
nome_arquivo="", elaborador=None, outliers_excluidos=None,
|
| 2029 |
-
periodo_dados_mercado=None, tipo_y=None, coluna_area=None
|
|
|
|
| 2030 |
"""
|
| 2031 |
Exporta o modelo em formato .dai v2 (estrutura nested).
|
| 2032 |
|
|
@@ -2041,6 +2051,7 @@ def exportar_modelo_dai(resultado_modelo, df_original, df_completo=None, estatis
|
|
| 2041 |
periodo_dados_mercado: dict com data_inicial e data_final dos dados de mercado
|
| 2042 |
tipo_y: natureza do Y ("unitario" ou "total")
|
| 2043 |
coluna_area: nome da coluna de área usada nas conversões
|
|
|
|
| 2044 |
|
| 2045 |
Retorna:
|
| 2046 |
tuple: (caminho_arquivo, mensagem)
|
|
@@ -2160,6 +2171,7 @@ def exportar_modelo_dai(resultado_modelo, df_original, df_completo=None, estatis
|
|
| 2160 |
pacote = {
|
| 2161 |
"versao": 2,
|
| 2162 |
"elaborador": elaborador,
|
|
|
|
| 2163 |
"periodo_dados_mercado": _normalizar_periodo_dados_mercado(periodo_dados_mercado),
|
| 2164 |
"avaliacao": normalizar_config_avaliacao_modelo(
|
| 2165 |
{"tipo_y": tipo_y, "coluna_area": coluna_area},
|
|
|
|
| 376 |
}
|
| 377 |
|
| 378 |
|
| 379 |
+
def normalizar_observacao_modelo(observacao):
|
| 380 |
+
"""Normaliza a observação livre salva junto do modelo."""
|
| 381 |
+
texto = str(observacao or "").strip()
|
| 382 |
+
return texto or None
|
| 383 |
+
|
| 384 |
+
|
| 385 |
def carregar_dai(caminho):
|
| 386 |
"""
|
| 387 |
Carrega arquivo .dai e extrai DataFrame, variáveis e transformações.
|
| 388 |
|
| 389 |
Retorna:
|
| 390 |
+
tuple: (df, coluna_y, colunas_x, transformacao_y, transformacoes_x, dicotomicas, codigo_alocado, percentuais, mensagem, sucesso, elaborador, observacao_modelo, outliers_excluidos, periodo_dados_mercado, config_avaliacao)
|
| 391 |
"""
|
| 392 |
try:
|
| 393 |
pacote = load(caminho)
|
|
|
|
| 464 |
df=df,
|
| 465 |
)
|
| 466 |
elaborador = pacote.get("elaborador", None)
|
| 467 |
+
observacao_modelo = normalizar_observacao_modelo(pacote.get("observacao_modelo"))
|
| 468 |
msg = f"Modelo .dai carregado: {os.path.basename(caminho)} ({len(df)} dados, Y={coluna_y}, {len(colunas_x)} variáveis X)"
|
| 469 |
return (
|
| 470 |
df,
|
|
|
|
| 478 |
msg,
|
| 479 |
True,
|
| 480 |
elaborador,
|
| 481 |
+
observacao_modelo,
|
| 482 |
outliers_excluidos,
|
| 483 |
periodo_dados_mercado,
|
| 484 |
config_avaliacao,
|
|
|
|
| 497 |
f"Erro ao carregar .dai: {str(e)}",
|
| 498 |
False,
|
| 499 |
None,
|
| 500 |
+
None,
|
| 501 |
[],
|
| 502 |
{"data_inicial": None, "data_final": None},
|
| 503 |
{"tipo_y": None, "coluna_area": None},
|
|
|
|
| 2035 |
|
| 2036 |
def exportar_modelo_dai(resultado_modelo, df_original, df_completo=None, estatisticas=None,
|
| 2037 |
nome_arquivo="", elaborador=None, outliers_excluidos=None,
|
| 2038 |
+
periodo_dados_mercado=None, tipo_y=None, coluna_area=None,
|
| 2039 |
+
observacao_modelo=None):
|
| 2040 |
"""
|
| 2041 |
Exporta o modelo em formato .dai v2 (estrutura nested).
|
| 2042 |
|
|
|
|
| 2051 |
periodo_dados_mercado: dict com data_inicial e data_final dos dados de mercado
|
| 2052 |
tipo_y: natureza do Y ("unitario" ou "total")
|
| 2053 |
coluna_area: nome da coluna de área usada nas conversões
|
| 2054 |
+
observacao_modelo: texto livre associado ao modelo
|
| 2055 |
|
| 2056 |
Retorna:
|
| 2057 |
tuple: (caminho_arquivo, mensagem)
|
|
|
|
| 2171 |
pacote = {
|
| 2172 |
"versao": 2,
|
| 2173 |
"elaborador": elaborador,
|
| 2174 |
+
"observacao_modelo": normalizar_observacao_modelo(observacao_modelo),
|
| 2175 |
"periodo_dados_mercado": _normalizar_periodo_dados_mercado(periodo_dados_mercado),
|
| 2176 |
"avaliacao": normalizar_config_avaliacao_modelo(
|
| 2177 |
{"tipo_y": tipo_y, "coluna_area": coluna_area},
|
backend/app/core/visualizacao/app.py
CHANGED
|
@@ -1460,6 +1460,7 @@ def _formatar_badge_completo(pacote, nome_modelo=""):
|
|
| 1460 |
return texto
|
| 1461 |
|
| 1462 |
model_name = str(nome_modelo or "").strip() or "-"
|
|
|
|
| 1463 |
|
| 1464 |
periodo = pacote.get("periodo_dados_mercado") or {}
|
| 1465 |
data_inicial = _data_br(periodo.get("data_inicial"))
|
|
@@ -1555,6 +1556,13 @@ def _formatar_badge_completo(pacote, nome_modelo=""):
|
|
| 1555 |
+ (f"<div class='elaborador-badge-meta'>{_esc(meta_txt)}</div>" if meta_txt else "")
|
| 1556 |
)
|
| 1557 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1558 |
|
| 1559 |
return (
|
| 1560 |
"<div class='subpanel section1-group'>"
|
|
@@ -1562,10 +1570,7 @@ def _formatar_badge_completo(pacote, nome_modelo=""):
|
|
| 1562 |
"<div class='modelo-info-card'>"
|
| 1563 |
"<div class='modelo-info-split'>"
|
| 1564 |
"<div class='modelo-info-col'>"
|
| 1565 |
-
|
| 1566 |
-
"<div class='elaborador-badge-title'>NOME DO MODELO:</div>"
|
| 1567 |
-
f"<div class='elaborador-badge-name'>{_esc(model_name)}</div>"
|
| 1568 |
-
"</div>"
|
| 1569 |
"<div class='modelo-info-stack-block'>"
|
| 1570 |
"<div class='elaborador-badge-title'>ELABORADO POR:</div>"
|
| 1571 |
+ elaborador_html +
|
|
|
|
| 1460 |
return texto
|
| 1461 |
|
| 1462 |
model_name = str(nome_modelo or "").strip() or "-"
|
| 1463 |
+
observacao_modelo = str(pacote.get("observacao_modelo") or "").strip()
|
| 1464 |
|
| 1465 |
periodo = pacote.get("periodo_dados_mercado") or {}
|
| 1466 |
data_inicial = _data_br(periodo.get("data_inicial"))
|
|
|
|
| 1556 |
+ (f"<div class='elaborador-badge-meta'>{_esc(meta_txt)}</div>" if meta_txt else "")
|
| 1557 |
)
|
| 1558 |
)
|
| 1559 |
+
nome_modelo_html = (
|
| 1560 |
+
"<div class='modelo-info-stack-block'>"
|
| 1561 |
+
"<div class='elaborador-badge-title'>NOME DO MODELO:</div>"
|
| 1562 |
+
f"<div class='elaborador-badge-name'>{_esc(model_name)}</div>"
|
| 1563 |
+
+ (f"<div class='elaborador-badge-meta'>{_esc(observacao_modelo)}</div>" if observacao_modelo else "")
|
| 1564 |
+
+ "</div>"
|
| 1565 |
+
)
|
| 1566 |
|
| 1567 |
return (
|
| 1568 |
"<div class='subpanel section1-group'>"
|
|
|
|
| 1570 |
"<div class='modelo-info-card'>"
|
| 1571 |
"<div class='modelo-info-split'>"
|
| 1572 |
"<div class='modelo-info-col'>"
|
| 1573 |
+
+ nome_modelo_html +
|
|
|
|
|
|
|
|
|
|
| 1574 |
"<div class='modelo-info-stack-block'>"
|
| 1575 |
"<div class='elaborador-badge-title'>ELABORADO POR:</div>"
|
| 1576 |
+ elaborador_html +
|
backend/app/models/session.py
CHANGED
|
@@ -56,6 +56,7 @@ class SessionState:
|
|
| 56 |
graficos_dispersao_cache: dict[str, dict[str, Any]] = field(default_factory=dict)
|
| 57 |
|
| 58 |
elaborador: dict[str, Any] | None = None
|
|
|
|
| 59 |
|
| 60 |
def reset_modelo(self) -> None:
|
| 61 |
self.resultados_busca = []
|
|
@@ -66,6 +67,7 @@ class SessionState:
|
|
| 66 |
self.transformacao_y = "(x)"
|
| 67 |
self.transformacoes_x = {}
|
| 68 |
self.graficos_dispersao_cache = {}
|
|
|
|
| 69 |
|
| 70 |
def reset_visualizacao(self) -> None:
|
| 71 |
self.pacote_visualizacao = None
|
|
|
|
| 56 |
graficos_dispersao_cache: dict[str, dict[str, Any]] = field(default_factory=dict)
|
| 57 |
|
| 58 |
elaborador: dict[str, Any] | None = None
|
| 59 |
+
observacao_modelo: str | None = None
|
| 60 |
|
| 61 |
def reset_modelo(self) -> None:
|
| 62 |
self.resultados_busca = []
|
|
|
|
| 67 |
self.transformacao_y = "(x)"
|
| 68 |
self.transformacoes_x = {}
|
| 69 |
self.graficos_dispersao_cache = {}
|
| 70 |
+
self.observacao_modelo = None
|
| 71 |
|
| 72 |
def reset_visualizacao(self) -> None:
|
| 73 |
self.pacote_visualizacao = None
|
backend/app/services/elaboracao_service.py
CHANGED
|
@@ -39,6 +39,7 @@ from app.core.elaboracao.core import (
|
|
| 39 |
exportar_base_csv,
|
| 40 |
exportar_modelo_dai,
|
| 41 |
identificar_coluna_y_padrao,
|
|
|
|
| 42 |
obter_colunas_numericas,
|
| 43 |
avaliar_imovel,
|
| 44 |
testar_micronumerosidade,
|
|
@@ -1011,6 +1012,7 @@ def load_dai_for_elaboracao(session: SessionState, caminho_arquivo: str) -> dict
|
|
| 1011 |
msg,
|
| 1012 |
sucesso,
|
| 1013 |
elaborador,
|
|
|
|
| 1014 |
outliers_excluidos,
|
| 1015 |
periodo_dados_mercado,
|
| 1016 |
config_avaliacao,
|
|
@@ -1020,10 +1022,12 @@ def load_dai_for_elaboracao(session: SessionState, caminho_arquivo: str) -> dict
|
|
| 1020 |
raise HTTPException(status_code=400, detail=msg)
|
| 1021 |
|
| 1022 |
session.elaborador = elaborador
|
|
|
|
| 1023 |
outliers_carregados = _clean_int_list(outliers_excluidos)
|
| 1024 |
periodo_normalizado = _normalizar_periodo_dados_mercado(periodo_dados_mercado)
|
| 1025 |
|
| 1026 |
base = _set_dataframe_base(session, df, clear_models=True)
|
|
|
|
| 1027 |
session.coluna_data_mercado = periodo_normalizado["coluna_data"]
|
| 1028 |
session.periodo_dados_mercado_inicio = periodo_normalizado["data_inicial"]
|
| 1029 |
session.periodo_dados_mercado_fim = periodo_normalizado["data_final"]
|
|
@@ -1069,6 +1073,7 @@ def load_dai_for_elaboracao(session: SessionState, caminho_arquivo: str) -> dict
|
|
| 1069 |
"outliers_html": html_outliers,
|
| 1070 |
"contexto": _selection_context(session),
|
| 1071 |
"elaborador": sanitize_value(elaborador),
|
|
|
|
| 1072 |
**_payload_data_mercado(session),
|
| 1073 |
}
|
| 1074 |
|
|
@@ -2373,7 +2378,12 @@ def aplicar_coluna_data_mercado(session: SessionState, coluna_data: str) -> dict
|
|
| 2373 |
}
|
| 2374 |
|
| 2375 |
|
| 2376 |
-
def exportar_modelo(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2377 |
if session.resultado_modelo is None:
|
| 2378 |
raise HTTPException(status_code=400, detail="Ajuste um modelo antes de exportar")
|
| 2379 |
if session.df_filtrado is None or session.df_original is None:
|
|
@@ -2382,6 +2392,10 @@ def exportar_modelo(session: SessionState, nome_arquivo: str, elaborador: dict[s
|
|
| 2382 |
if not nome_arquivo or not nome_arquivo.strip():
|
| 2383 |
raise HTTPException(status_code=400, detail="Informe o nome do arquivo")
|
| 2384 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2385 |
caminho, msg = exportar_modelo_dai(
|
| 2386 |
session.resultado_modelo,
|
| 2387 |
session.df_filtrado,
|
|
@@ -2397,6 +2411,7 @@ def exportar_modelo(session: SessionState, nome_arquivo: str, elaborador: dict[s
|
|
| 2397 |
},
|
| 2398 |
tipo_y=session.tipo_y,
|
| 2399 |
coluna_area=session.coluna_area,
|
|
|
|
| 2400 |
)
|
| 2401 |
|
| 2402 |
if not caminho:
|
|
@@ -2405,6 +2420,46 @@ def exportar_modelo(session: SessionState, nome_arquivo: str, elaborador: dict[s
|
|
| 2405 |
return caminho, msg
|
| 2406 |
|
| 2407 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2408 |
def exportar_base(session: SessionState, usar_filtrado: bool = True) -> str:
|
| 2409 |
df = session.df_filtrado if usar_filtrado else session.df_original
|
| 2410 |
caminho = exportar_base_csv(df)
|
|
|
|
| 39 |
exportar_base_csv,
|
| 40 |
exportar_modelo_dai,
|
| 41 |
identificar_coluna_y_padrao,
|
| 42 |
+
normalizar_observacao_modelo,
|
| 43 |
obter_colunas_numericas,
|
| 44 |
avaliar_imovel,
|
| 45 |
testar_micronumerosidade,
|
|
|
|
| 1012 |
msg,
|
| 1013 |
sucesso,
|
| 1014 |
elaborador,
|
| 1015 |
+
observacao_modelo,
|
| 1016 |
outliers_excluidos,
|
| 1017 |
periodo_dados_mercado,
|
| 1018 |
config_avaliacao,
|
|
|
|
| 1022 |
raise HTTPException(status_code=400, detail=msg)
|
| 1023 |
|
| 1024 |
session.elaborador = elaborador
|
| 1025 |
+
observacao_modelo_normalizada = normalizar_observacao_modelo(observacao_modelo)
|
| 1026 |
outliers_carregados = _clean_int_list(outliers_excluidos)
|
| 1027 |
periodo_normalizado = _normalizar_periodo_dados_mercado(periodo_dados_mercado)
|
| 1028 |
|
| 1029 |
base = _set_dataframe_base(session, df, clear_models=True)
|
| 1030 |
+
session.observacao_modelo = observacao_modelo_normalizada
|
| 1031 |
session.coluna_data_mercado = periodo_normalizado["coluna_data"]
|
| 1032 |
session.periodo_dados_mercado_inicio = periodo_normalizado["data_inicial"]
|
| 1033 |
session.periodo_dados_mercado_fim = periodo_normalizado["data_final"]
|
|
|
|
| 1073 |
"outliers_html": html_outliers,
|
| 1074 |
"contexto": _selection_context(session),
|
| 1075 |
"elaborador": sanitize_value(elaborador),
|
| 1076 |
+
"observacao_modelo": sanitize_value(observacao_modelo_normalizada),
|
| 1077 |
**_payload_data_mercado(session),
|
| 1078 |
}
|
| 1079 |
|
|
|
|
| 2378 |
}
|
| 2379 |
|
| 2380 |
|
| 2381 |
+
def exportar_modelo(
|
| 2382 |
+
session: SessionState,
|
| 2383 |
+
nome_arquivo: str,
|
| 2384 |
+
elaborador: dict[str, Any] | None = None,
|
| 2385 |
+
observacao_modelo: str | None = None,
|
| 2386 |
+
) -> tuple[str, str]:
|
| 2387 |
if session.resultado_modelo is None:
|
| 2388 |
raise HTTPException(status_code=400, detail="Ajuste um modelo antes de exportar")
|
| 2389 |
if session.df_filtrado is None or session.df_original is None:
|
|
|
|
| 2392 |
if not nome_arquivo or not nome_arquivo.strip():
|
| 2393 |
raise HTTPException(status_code=400, detail="Informe o nome do arquivo")
|
| 2394 |
|
| 2395 |
+
session.observacao_modelo = normalizar_observacao_modelo(
|
| 2396 |
+
observacao_modelo if observacao_modelo is not None else session.observacao_modelo
|
| 2397 |
+
)
|
| 2398 |
+
|
| 2399 |
caminho, msg = exportar_modelo_dai(
|
| 2400 |
session.resultado_modelo,
|
| 2401 |
session.df_filtrado,
|
|
|
|
| 2411 |
},
|
| 2412 |
tipo_y=session.tipo_y,
|
| 2413 |
coluna_area=session.coluna_area,
|
| 2414 |
+
observacao_modelo=session.observacao_modelo,
|
| 2415 |
)
|
| 2416 |
|
| 2417 |
if not caminho:
|
|
|
|
| 2420 |
return caminho, msg
|
| 2421 |
|
| 2422 |
|
| 2423 |
+
def salvar_modelo_repositorio(
|
| 2424 |
+
session: SessionState,
|
| 2425 |
+
nome_arquivo: str,
|
| 2426 |
+
elaborador: dict[str, Any] | None = None,
|
| 2427 |
+
observacao_modelo: str | None = None,
|
| 2428 |
+
actor: str | None = None,
|
| 2429 |
+
confirmar_substituicao: bool = False,
|
| 2430 |
+
) -> dict[str, Any]:
|
| 2431 |
+
resolved = model_repository.resolve_model_repository()
|
| 2432 |
+
if resolved.provider != "hf_dataset":
|
| 2433 |
+
raise HTTPException(
|
| 2434 |
+
status_code=400,
|
| 2435 |
+
detail="Salvar direto no repositorio so esta disponivel quando o repositorio usa HF Dataset.",
|
| 2436 |
+
)
|
| 2437 |
+
|
| 2438 |
+
caminho, _ = exportar_modelo(
|
| 2439 |
+
session,
|
| 2440 |
+
nome_arquivo,
|
| 2441 |
+
elaborador=elaborador,
|
| 2442 |
+
observacao_modelo=observacao_modelo,
|
| 2443 |
+
)
|
| 2444 |
+
arquivo = Path(caminho)
|
| 2445 |
+
if not arquivo.exists():
|
| 2446 |
+
raise HTTPException(status_code=500, detail="Falha ao gerar o arquivo .dai para envio ao repositorio.")
|
| 2447 |
+
|
| 2448 |
+
resultado = model_repository.upsert_repository_models(
|
| 2449 |
+
[(arquivo.name, arquivo.read_bytes())],
|
| 2450 |
+
actor=actor,
|
| 2451 |
+
confirmar_substituicao=confirmar_substituicao,
|
| 2452 |
+
)
|
| 2453 |
+
contexto = listar_modelos_repositorio()
|
| 2454 |
+
return sanitize_value(
|
| 2455 |
+
{
|
| 2456 |
+
"status": "Modelo salvo no repositorio.",
|
| 2457 |
+
"resultado_upload": resultado,
|
| 2458 |
+
**contexto,
|
| 2459 |
+
}
|
| 2460 |
+
)
|
| 2461 |
+
|
| 2462 |
+
|
| 2463 |
def exportar_base(session: SessionState, usar_filtrado: bool = True) -> str:
|
| 2464 |
df = session.df_filtrado if usar_filtrado else session.df_original
|
| 2465 |
caminho = exportar_base_csv(df)
|
backend/app/services/visualizacao_service.py
CHANGED
|
@@ -20,6 +20,7 @@ from app.core.elaboracao.core import (
|
|
| 20 |
exportar_avaliacoes_excel,
|
| 21 |
normalizar_coluna_area,
|
| 22 |
normalizar_config_avaliacao_modelo,
|
|
|
|
| 23 |
)
|
| 24 |
from app.core.elaboracao.formatadores import formatar_avaliacao_html
|
| 25 |
from app.models.session import SessionState
|
|
@@ -253,6 +254,7 @@ def carregar_modelo(session: SessionState, caminho_arquivo: str) -> dict[str, An
|
|
| 253 |
"status": "",
|
| 254 |
"badge_html": badge_html,
|
| 255 |
"nome_modelo": nome_modelo,
|
|
|
|
| 256 |
}
|
| 257 |
|
| 258 |
|
|
@@ -297,6 +299,7 @@ def _extrair_modelo_info(pacote: dict[str, Any]) -> dict[str, Any]:
|
|
| 297 |
"nome_y": nome_y.strip(),
|
| 298 |
"tipo_y": config_avaliacao.get("tipo_y"),
|
| 299 |
"coluna_area": config_avaliacao.get("coluna_area"),
|
|
|
|
| 300 |
"transformacao_y": transformacao_y,
|
| 301 |
"colunas_x": colunas_x,
|
| 302 |
"transformacoes_x": transformacoes_x,
|
|
|
|
| 20 |
exportar_avaliacoes_excel,
|
| 21 |
normalizar_coluna_area,
|
| 22 |
normalizar_config_avaliacao_modelo,
|
| 23 |
+
normalizar_observacao_modelo,
|
| 24 |
)
|
| 25 |
from app.core.elaboracao.formatadores import formatar_avaliacao_html
|
| 26 |
from app.models.session import SessionState
|
|
|
|
| 254 |
"status": "",
|
| 255 |
"badge_html": badge_html,
|
| 256 |
"nome_modelo": nome_modelo,
|
| 257 |
+
"observacao_modelo": normalizar_observacao_modelo(pacote.get("observacao_modelo")),
|
| 258 |
}
|
| 259 |
|
| 260 |
|
|
|
|
| 299 |
"nome_y": nome_y.strip(),
|
| 300 |
"tipo_y": config_avaliacao.get("tipo_y"),
|
| 301 |
"coluna_area": config_avaliacao.get("coluna_area"),
|
| 302 |
+
"observacao_modelo": normalizar_observacao_modelo(pacote.get("observacao_modelo")),
|
| 303 |
"transformacao_y": transformacao_y,
|
| 304 |
"colunas_x": colunas_x,
|
| 305 |
"transformacoes_x": transformacoes_x,
|
frontend/src/App.jsx
CHANGED
|
@@ -587,7 +587,7 @@ export default function App() {
|
|
| 587 |
</div>
|
| 588 |
|
| 589 |
<div className="tab-pane" hidden={activeTab !== 'Elaboração/Edição'}>
|
| 590 |
-
<ElaboracaoTab sessionId={sessionId} />
|
| 591 |
</div>
|
| 592 |
|
| 593 |
<div className="tab-pane" hidden={activeTab !== 'Repositório de Modelos'}>
|
|
|
|
| 587 |
</div>
|
| 588 |
|
| 589 |
<div className="tab-pane" hidden={activeTab !== 'Elaboração/Edição'}>
|
| 590 |
+
<ElaboracaoTab sessionId={sessionId} authUser={authUser} />
|
| 591 |
</div>
|
| 592 |
|
| 593 |
<div className="tab-pane" hidden={activeTab !== 'Repositório de Modelos'}>
|
frontend/src/api.js
CHANGED
|
@@ -270,16 +270,28 @@ export const api = {
|
|
| 270 |
}),
|
| 271 |
exportEvaluationElab: async (sessionId) => getBlob(`/api/elaboracao/evaluation/export?session_id=${encodeURIComponent(sessionId)}`),
|
| 272 |
exportEquationElab: async (sessionId, mode = 'excel') => getBlob(`/api/elaboracao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`),
|
| 273 |
-
exportModel: async (sessionId, nomeArquivo, elaborador) => {
|
| 274 |
const path = '/api/elaboracao/export-model'
|
| 275 |
const response = await fetchWithDiagnostics(path, {
|
| 276 |
method: 'POST',
|
| 277 |
headers: authHeaders({ 'Content-Type': 'application/json' }),
|
| 278 |
-
body: JSON.stringify({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
})
|
| 280 |
await handleResponse(response, path)
|
| 281 |
return responseBlobWithDiagnostics(response, path)
|
| 282 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
exportBase: (sessionId, filtered = true) => getBlob(`/api/elaboracao/export-base?session_id=${encodeURIComponent(sessionId)}&filtered=${String(filtered)}`),
|
| 284 |
updateElaboracaoMap: (sessionId, variavelMapa, modoMapa = 'pontos') => postJson('/api/elaboracao/map/update', {
|
| 285 |
session_id: sessionId,
|
|
|
|
| 270 |
}),
|
| 271 |
exportEvaluationElab: async (sessionId) => getBlob(`/api/elaboracao/evaluation/export?session_id=${encodeURIComponent(sessionId)}`),
|
| 272 |
exportEquationElab: async (sessionId, mode = 'excel') => getBlob(`/api/elaboracao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`),
|
| 273 |
+
exportModel: async (sessionId, nomeArquivo, elaborador, observacaoModelo = '') => {
|
| 274 |
const path = '/api/elaboracao/export-model'
|
| 275 |
const response = await fetchWithDiagnostics(path, {
|
| 276 |
method: 'POST',
|
| 277 |
headers: authHeaders({ 'Content-Type': 'application/json' }),
|
| 278 |
+
body: JSON.stringify({
|
| 279 |
+
session_id: sessionId,
|
| 280 |
+
nome_arquivo: nomeArquivo,
|
| 281 |
+
elaborador,
|
| 282 |
+
observacao_modelo: observacaoModelo,
|
| 283 |
+
}),
|
| 284 |
})
|
| 285 |
await handleResponse(response, path)
|
| 286 |
return responseBlobWithDiagnostics(response, path)
|
| 287 |
},
|
| 288 |
+
saveModelRepository: (sessionId, nomeArquivo, elaborador, observacaoModelo = '', confirmarSubstituicao = false) => postJson('/api/elaboracao/save-model-repository', {
|
| 289 |
+
session_id: sessionId,
|
| 290 |
+
nome_arquivo: nomeArquivo,
|
| 291 |
+
elaborador,
|
| 292 |
+
observacao_modelo: observacaoModelo,
|
| 293 |
+
confirmar_substituicao: confirmarSubstituicao,
|
| 294 |
+
}),
|
| 295 |
exportBase: (sessionId, filtered = true) => getBlob(`/api/elaboracao/export-base?session_id=${encodeURIComponent(sessionId)}&filtered=${String(filtered)}`),
|
| 296 |
updateElaboracaoMap: (sessionId, variavelMapa, modoMapa = 'pontos') => postJson('/api/elaboracao/map/update', {
|
| 297 |
session_id: sessionId,
|
frontend/src/components/AvaliacaoTab.jsx
CHANGED
|
@@ -444,6 +444,19 @@ function formatarFonteRepositorio(fonte) {
|
|
| 444 |
return 'Fonte: pasta local'
|
| 445 |
}
|
| 446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
const BASE_COMPARACAO_SEM_BASE = '__none__'
|
| 448 |
|
| 449 |
export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
|
|
@@ -451,6 +464,7 @@ export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
|
|
| 451 |
const [error, setError] = useState('')
|
| 452 |
const [status, setStatus] = useState('')
|
| 453 |
const [badgeHtml, setBadgeHtml] = useState('')
|
|
|
|
| 454 |
|
| 455 |
const [uploadedFile, setUploadedFile] = useState(null)
|
| 456 |
const [uploadDragOver, setUploadDragOver] = useState(false)
|
|
@@ -688,6 +702,7 @@ export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
|
|
| 688 |
resetCamposAvaliacao(campos)
|
| 689 |
setEquacaoSab(String(resp?.equacoes?.excel_sab || ''))
|
| 690 |
setModeloAtualNome(String(nomeModelo || '').trim())
|
|
|
|
| 691 |
}
|
| 692 |
|
| 693 |
async function withBusy(fn) {
|
|
@@ -730,7 +745,7 @@ export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
|
|
| 730 |
await withBusy(async () => {
|
| 731 |
const uploadResp = await api.uploadVisualizacaoFile(sessionId, arquivoUpload)
|
| 732 |
setStatus(uploadResp?.status || '')
|
| 733 |
-
setBadgeHtml(uploadResp?.badge_html || '')
|
| 734 |
const nomeModelo = uploadResp?.nome_modelo || arquivoUpload.name || ''
|
| 735 |
const contextoResp = await api.evaluationContextViz(sessionId)
|
| 736 |
aplicarRespostaExibicao(contextoResp, nomeModelo)
|
|
@@ -753,7 +768,7 @@ export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
|
|
| 753 |
const tentativas = [alvoModeloId, ...(fallbackChaves || [])]
|
| 754 |
const { uploadResp, modeloIdUsado } = await carregarModeloRepositorioComFallback(tentativas)
|
| 755 |
setStatus(uploadResp?.status || '')
|
| 756 |
-
setBadgeHtml(uploadResp?.badge_html || '')
|
| 757 |
setRepoModeloSelecionado(modeloIdUsado)
|
| 758 |
const modeloSelecionado = repoModeloOptions.find((item) => item.value === modeloIdUsado)
|
| 759 |
const nomeModelo = uploadResp?.nome_modelo || modeloSelecionado?.label || ''
|
|
@@ -1110,6 +1125,12 @@ export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
|
|
| 1110 |
)}
|
| 1111 |
{status ? <div className="status-line">{status}</div> : null}
|
| 1112 |
{badgeHtml ? <div className="upload-badge-block" dangerouslySetInnerHTML={{ __html: badgeHtml }} /> : null}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1113 |
</div>
|
| 1114 |
|
| 1115 |
{!modeloPronto ? (
|
|
|
|
| 444 |
return 'Fonte: pasta local'
|
| 445 |
}
|
| 446 |
|
| 447 |
+
function removerObservacaoDoBadgeHtml(html) {
|
| 448 |
+
const markup = String(html || '').trim()
|
| 449 |
+
if (!markup || typeof document === 'undefined') return markup
|
| 450 |
+
|
| 451 |
+
const root = document.createElement('div')
|
| 452 |
+
root.innerHTML = markup
|
| 453 |
+
const observacao = root.querySelector('.modelo-info-stack-block:first-child .elaborador-badge-meta')
|
| 454 |
+
if (observacao) {
|
| 455 |
+
observacao.remove()
|
| 456 |
+
}
|
| 457 |
+
return root.innerHTML
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
const BASE_COMPARACAO_SEM_BASE = '__none__'
|
| 461 |
|
| 462 |
export default function AvaliacaoTab({ sessionId, quickLoadRequest = null }) {
|
|
|
|
| 464 |
const [error, setError] = useState('')
|
| 465 |
const [status, setStatus] = useState('')
|
| 466 |
const [badgeHtml, setBadgeHtml] = useState('')
|
| 467 |
+
const [modeloObservacao, setModeloObservacao] = useState('')
|
| 468 |
|
| 469 |
const [uploadedFile, setUploadedFile] = useState(null)
|
| 470 |
const [uploadDragOver, setUploadDragOver] = useState(false)
|
|
|
|
| 702 |
resetCamposAvaliacao(campos)
|
| 703 |
setEquacaoSab(String(resp?.equacoes?.excel_sab || ''))
|
| 704 |
setModeloAtualNome(String(nomeModelo || '').trim())
|
| 705 |
+
setModeloObservacao(String(resp?.meta_modelo?.observacao_modelo || '').trim())
|
| 706 |
}
|
| 707 |
|
| 708 |
async function withBusy(fn) {
|
|
|
|
| 745 |
await withBusy(async () => {
|
| 746 |
const uploadResp = await api.uploadVisualizacaoFile(sessionId, arquivoUpload)
|
| 747 |
setStatus(uploadResp?.status || '')
|
| 748 |
+
setBadgeHtml(removerObservacaoDoBadgeHtml(uploadResp?.badge_html || ''))
|
| 749 |
const nomeModelo = uploadResp?.nome_modelo || arquivoUpload.name || ''
|
| 750 |
const contextoResp = await api.evaluationContextViz(sessionId)
|
| 751 |
aplicarRespostaExibicao(contextoResp, nomeModelo)
|
|
|
|
| 768 |
const tentativas = [alvoModeloId, ...(fallbackChaves || [])]
|
| 769 |
const { uploadResp, modeloIdUsado } = await carregarModeloRepositorioComFallback(tentativas)
|
| 770 |
setStatus(uploadResp?.status || '')
|
| 771 |
+
setBadgeHtml(removerObservacaoDoBadgeHtml(uploadResp?.badge_html || ''))
|
| 772 |
setRepoModeloSelecionado(modeloIdUsado)
|
| 773 |
const modeloSelecionado = repoModeloOptions.find((item) => item.value === modeloIdUsado)
|
| 774 |
const nomeModelo = uploadResp?.nome_modelo || modeloSelecionado?.label || ''
|
|
|
|
| 1125 |
)}
|
| 1126 |
{status ? <div className="status-line">{status}</div> : null}
|
| 1127 |
{badgeHtml ? <div className="upload-badge-block" dangerouslySetInnerHTML={{ __html: badgeHtml }} /> : null}
|
| 1128 |
+
{modeloObservacao ? (
|
| 1129 |
+
<div className="elaborador-badge modelo-observacao-badge">
|
| 1130 |
+
<div className="elaborador-badge-title">OBSERVAÇÃO</div>
|
| 1131 |
+
<div className="modelo-observacao-text">{modeloObservacao}</div>
|
| 1132 |
+
</div>
|
| 1133 |
+
) : null}
|
| 1134 |
</div>
|
| 1135 |
|
| 1136 |
{!modeloPronto ? (
|
frontend/src/components/ElaboracaoTab.jsx
CHANGED
|
@@ -745,6 +745,7 @@ function buildLoadedModelInfo(resp) {
|
|
| 745 |
|
| 746 |
return {
|
| 747 |
nome_modelo: nomeModelo,
|
|
|
|
| 748 |
coluna_y: colunaY,
|
| 749 |
tipo_y: normalizarTipoY(contexto.tipo_y),
|
| 750 |
coluna_area: String(contexto.coluna_area || '').trim(),
|
|
@@ -859,7 +860,7 @@ function DiagnosticPngCard({ title, pngPayload, alt }) {
|
|
| 859 |
)
|
| 860 |
}
|
| 861 |
|
| 862 |
-
export default function ElaboracaoTab({ sessionId }) {
|
| 863 |
const [loading, setLoading] = useState(false)
|
| 864 |
const [downloadingAssets, setDownloadingAssets] = useState(false)
|
| 865 |
const [error, setError] = useState('')
|
|
@@ -877,6 +878,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 877 |
const [repoModelos, setRepoModelos] = useState([])
|
| 878 |
const [repoModeloSelecionado, setRepoModeloSelecionado] = useState('')
|
| 879 |
const [repoModelosLoading, setRepoModelosLoading] = useState(false)
|
|
|
|
| 880 |
const [repoFonteModelos, setRepoFonteModelos] = useState('')
|
| 881 |
const [repoModeloDropdownOpen, setRepoModeloDropdownOpen] = useState(false)
|
| 882 |
|
|
@@ -984,8 +986,10 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 984 |
const [knnDetalheInfo, setKnnDetalheInfo] = useState(null)
|
| 985 |
|
| 986 |
const [nomeArquivoExport, setNomeArquivoExport] = useState('')
|
|
|
|
| 987 |
const [avaliadores, setAvaliadores] = useState([])
|
| 988 |
const [avaliadorSelecionado, setAvaliadorSelecionado] = useState('')
|
|
|
|
| 989 |
const [tipoFonteDados, setTipoFonteDados] = useState('')
|
| 990 |
const [selectionAppliedSnapshot, setSelectionAppliedSnapshot] = useState(() => buildSelectionSnapshot())
|
| 991 |
const [buscaTransformAppliedSnapshot, setBuscaTransformAppliedSnapshot] = useState(() => buildGrauSnapshot(0, 0))
|
|
@@ -1027,6 +1031,14 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1027 |
[colunasX, colunasXDisponiveis],
|
| 1028 |
)
|
| 1029 |
const conselhoRegistro = useMemo(() => formatConselhoRegistro(elaborador), [elaborador])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1030 |
const elaboradorMeta = useMemo(() => {
|
| 1031 |
if (!elaborador) return []
|
| 1032 |
return [elaborador.cargo, conselhoRegistro, elaborador.matricula_sem_digito ? `Matricula ${elaborador.matricula_sem_digito}` : '', elaborador.lotacao].filter(Boolean)
|
|
@@ -1901,6 +1913,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1901 |
if (!ativo) return
|
| 1902 |
setRepoModelos([])
|
| 1903 |
setRepoModeloSelecionado('')
|
|
|
|
| 1904 |
setRepoFonteModelos('')
|
| 1905 |
})
|
| 1906 |
.finally(() => {
|
|
@@ -1966,9 +1979,19 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1966 |
return 'Fonte: pasta local'
|
| 1967 |
}
|
| 1968 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1969 |
function aplicarRespostaModelosRepositorio(resp) {
|
| 1970 |
const modelos = Array.isArray(resp?.modelos) ? resp.modelos : []
|
| 1971 |
setRepoModelos(modelos)
|
|
|
|
| 1972 |
setRepoFonteModelos(formatarFonteRepositorio(resp?.fonte || null))
|
| 1973 |
setRepoModeloSelecionado((prev) => {
|
| 1974 |
const atual = String(prev || '')
|
|
@@ -1988,6 +2011,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1988 |
setImportacaoErro(err.message || 'Falha ao carregar modelos do repositório.')
|
| 1989 |
setRepoModelos([])
|
| 1990 |
setRepoModeloSelecionado('')
|
|
|
|
| 1991 |
setRepoFonteModelos('')
|
| 1992 |
} finally {
|
| 1993 |
setRepoModelosLoading(false)
|
|
@@ -2273,6 +2297,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 2273 |
setGeoFalhasHtml('')
|
| 2274 |
setGeoCorrecoes([])
|
| 2275 |
setElaborador(resp.elaborador || null)
|
|
|
|
| 2276 |
setModeloCarregadoInfo(buildLoadedModelInfo(resp))
|
| 2277 |
setAvaliadorSelecionado(resp.elaborador?.nome_completo || '')
|
| 2278 |
const fileName = String(meta.fileName || uploadedFile?.name || arquivoCarregadoInfo?.nome_arquivo || '').trim()
|
|
@@ -2477,6 +2502,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 2477 |
setGeoFalhasHtml('')
|
| 2478 |
setGeoCorrecoes([])
|
| 2479 |
setElaborador(resp.elaborador || null)
|
|
|
|
| 2480 |
setModeloCarregadoInfo(null)
|
| 2481 |
setAvaliadorSelecionado(resp.elaborador?.nome_completo || '')
|
| 2482 |
setRequiresSheet(false)
|
|
@@ -3245,12 +3271,71 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3245 |
const nomeExport = String(nomeArquivoExport || '').trim()
|
| 3246 |
if (!sessionId || !nomeExport) return
|
| 3247 |
await withBusy(async () => {
|
|
|
|
| 3248 |
const avaliadorEscolhido = avaliadores.find((item) => item.nome_completo === avaliadorSelecionado) || null
|
| 3249 |
-
const blob = await api.exportModel(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3250 |
downloadBlob(blob, `${nomeExport.replace(/\.dai$/i, '')}.dai`)
|
| 3251 |
})
|
| 3252 |
}
|
| 3253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3254 |
async function onExportBase() {
|
| 3255 |
if (!sessionId) return
|
| 3256 |
await withBusy(async () => {
|
|
@@ -3903,6 +3988,12 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3903 |
</div>
|
| 3904 |
</div>
|
| 3905 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3906 |
</>
|
| 3907 |
) : null}
|
| 3908 |
</div>
|
|
@@ -5830,26 +5921,94 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 5830 |
</SectionBlock>
|
| 5831 |
|
| 5832 |
<SectionBlock step="19" title="Exportar Modelo" subtitle="Geração do pacote .dai e download da base tratada.">
|
| 5833 |
-
<div className="
|
| 5834 |
-
<
|
| 5835 |
-
|
| 5836 |
-
|
| 5837 |
-
|
| 5838 |
-
|
| 5839 |
-
|
| 5840 |
-
|
| 5841 |
-
|
| 5842 |
-
|
| 5843 |
-
|
| 5844 |
-
|
| 5845 |
-
|
| 5846 |
-
|
| 5847 |
-
|
| 5848 |
-
|
| 5849 |
-
</
|
| 5850 |
-
|
| 5851 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5852 |
</div>
|
|
|
|
| 5853 |
</SectionBlock>
|
| 5854 |
</>
|
| 5855 |
) : null}
|
|
|
|
| 745 |
|
| 746 |
return {
|
| 747 |
nome_modelo: nomeModelo,
|
| 748 |
+
observacao_modelo: String(resp?.observacao_modelo || '').trim(),
|
| 749 |
coluna_y: colunaY,
|
| 750 |
tipo_y: normalizarTipoY(contexto.tipo_y),
|
| 751 |
coluna_area: String(contexto.coluna_area || '').trim(),
|
|
|
|
| 860 |
)
|
| 861 |
}
|
| 862 |
|
| 863 |
+
export default function ElaboracaoTab({ sessionId, authUser }) {
|
| 864 |
const [loading, setLoading] = useState(false)
|
| 865 |
const [downloadingAssets, setDownloadingAssets] = useState(false)
|
| 866 |
const [error, setError] = useState('')
|
|
|
|
| 878 |
const [repoModelos, setRepoModelos] = useState([])
|
| 879 |
const [repoModeloSelecionado, setRepoModeloSelecionado] = useState('')
|
| 880 |
const [repoModelosLoading, setRepoModelosLoading] = useState(false)
|
| 881 |
+
const [repoFonteInfo, setRepoFonteInfo] = useState(null)
|
| 882 |
const [repoFonteModelos, setRepoFonteModelos] = useState('')
|
| 883 |
const [repoModeloDropdownOpen, setRepoModeloDropdownOpen] = useState(false)
|
| 884 |
|
|
|
|
| 986 |
const [knnDetalheInfo, setKnnDetalheInfo] = useState(null)
|
| 987 |
|
| 988 |
const [nomeArquivoExport, setNomeArquivoExport] = useState('')
|
| 989 |
+
const [observacaoModelo, setObservacaoModelo] = useState('')
|
| 990 |
const [avaliadores, setAvaliadores] = useState([])
|
| 991 |
const [avaliadorSelecionado, setAvaliadorSelecionado] = useState('')
|
| 992 |
+
const [repositorioSaveStatus, setRepositorioSaveStatus] = useState('')
|
| 993 |
const [tipoFonteDados, setTipoFonteDados] = useState('')
|
| 994 |
const [selectionAppliedSnapshot, setSelectionAppliedSnapshot] = useState(() => buildSelectionSnapshot())
|
| 995 |
const [buscaTransformAppliedSnapshot, setBuscaTransformAppliedSnapshot] = useState(() => buildGrauSnapshot(0, 0))
|
|
|
|
| 1031 |
[colunasX, colunasXDisponiveis],
|
| 1032 |
)
|
| 1033 |
const conselhoRegistro = useMemo(() => formatConselhoRegistro(elaborador), [elaborador])
|
| 1034 |
+
const repositorioUploadAdminHabilitado = useMemo(
|
| 1035 |
+
() => String(authUser?.perfil || '').toLowerCase() === 'admin',
|
| 1036 |
+
[authUser],
|
| 1037 |
+
)
|
| 1038 |
+
const repositorioSalvamentoDiretoHabilitado = useMemo(
|
| 1039 |
+
() => repositorioUploadAdminHabilitado && String(repoFonteInfo?.provider || '').toLowerCase() === 'hf_dataset',
|
| 1040 |
+
[repoFonteInfo, repositorioUploadAdminHabilitado],
|
| 1041 |
+
)
|
| 1042 |
const elaboradorMeta = useMemo(() => {
|
| 1043 |
if (!elaborador) return []
|
| 1044 |
return [elaborador.cargo, conselhoRegistro, elaborador.matricula_sem_digito ? `Matricula ${elaborador.matricula_sem_digito}` : '', elaborador.lotacao].filter(Boolean)
|
|
|
|
| 1913 |
if (!ativo) return
|
| 1914 |
setRepoModelos([])
|
| 1915 |
setRepoModeloSelecionado('')
|
| 1916 |
+
setRepoFonteInfo(null)
|
| 1917 |
setRepoFonteModelos('')
|
| 1918 |
})
|
| 1919 |
.finally(() => {
|
|
|
|
| 1979 |
return 'Fonte: pasta local'
|
| 1980 |
}
|
| 1981 |
|
| 1982 |
+
function parseSubstituidosErro(err) {
|
| 1983 |
+
if (!err || typeof err !== 'object') return []
|
| 1984 |
+
const detail = err.detail
|
| 1985 |
+
if (!detail || typeof detail !== 'object') return []
|
| 1986 |
+
const lista = detail.substituidos
|
| 1987 |
+
if (!Array.isArray(lista)) return []
|
| 1988 |
+
return lista.map((item) => String(item || '').trim()).filter(Boolean)
|
| 1989 |
+
}
|
| 1990 |
+
|
| 1991 |
function aplicarRespostaModelosRepositorio(resp) {
|
| 1992 |
const modelos = Array.isArray(resp?.modelos) ? resp.modelos : []
|
| 1993 |
setRepoModelos(modelos)
|
| 1994 |
+
setRepoFonteInfo(resp?.fonte || null)
|
| 1995 |
setRepoFonteModelos(formatarFonteRepositorio(resp?.fonte || null))
|
| 1996 |
setRepoModeloSelecionado((prev) => {
|
| 1997 |
const atual = String(prev || '')
|
|
|
|
| 2011 |
setImportacaoErro(err.message || 'Falha ao carregar modelos do repositório.')
|
| 2012 |
setRepoModelos([])
|
| 2013 |
setRepoModeloSelecionado('')
|
| 2014 |
+
setRepoFonteInfo(null)
|
| 2015 |
setRepoFonteModelos('')
|
| 2016 |
} finally {
|
| 2017 |
setRepoModelosLoading(false)
|
|
|
|
| 2297 |
setGeoFalhasHtml('')
|
| 2298 |
setGeoCorrecoes([])
|
| 2299 |
setElaborador(resp.elaborador || null)
|
| 2300 |
+
setObservacaoModelo(String(resp?.observacao_modelo || '').trim())
|
| 2301 |
setModeloCarregadoInfo(buildLoadedModelInfo(resp))
|
| 2302 |
setAvaliadorSelecionado(resp.elaborador?.nome_completo || '')
|
| 2303 |
const fileName = String(meta.fileName || uploadedFile?.name || arquivoCarregadoInfo?.nome_arquivo || '').trim()
|
|
|
|
| 2502 |
setGeoFalhasHtml('')
|
| 2503 |
setGeoCorrecoes([])
|
| 2504 |
setElaborador(resp.elaborador || null)
|
| 2505 |
+
setObservacaoModelo(String(resp?.observacao_modelo || '').trim())
|
| 2506 |
setModeloCarregadoInfo(null)
|
| 2507 |
setAvaliadorSelecionado(resp.elaborador?.nome_completo || '')
|
| 2508 |
setRequiresSheet(false)
|
|
|
|
| 3271 |
const nomeExport = String(nomeArquivoExport || '').trim()
|
| 3272 |
if (!sessionId || !nomeExport) return
|
| 3273 |
await withBusy(async () => {
|
| 3274 |
+
setRepositorioSaveStatus('')
|
| 3275 |
const avaliadorEscolhido = avaliadores.find((item) => item.nome_completo === avaliadorSelecionado) || null
|
| 3276 |
+
const blob = await api.exportModel(
|
| 3277 |
+
sessionId,
|
| 3278 |
+
nomeExport,
|
| 3279 |
+
avaliadorEscolhido || elaborador || null,
|
| 3280 |
+
String(observacaoModelo || '').trim(),
|
| 3281 |
+
)
|
| 3282 |
downloadBlob(blob, `${nomeExport.replace(/\.dai$/i, '')}.dai`)
|
| 3283 |
})
|
| 3284 |
}
|
| 3285 |
|
| 3286 |
+
async function onSaveModelRepository() {
|
| 3287 |
+
const nomeExport = String(nomeArquivoExport || '').trim()
|
| 3288 |
+
if (!sessionId || !nomeExport || !repositorioSalvamentoDiretoHabilitado) return
|
| 3289 |
+
|
| 3290 |
+
const senha = window.prompt('Digite a senha para salvar o modelo diretamente no repositório:')
|
| 3291 |
+
if (senha === null) return
|
| 3292 |
+
if (senha !== '123456') {
|
| 3293 |
+
setRepositorioSaveStatus('')
|
| 3294 |
+
setError('Senha incorreta para salvar no repositório.')
|
| 3295 |
+
return
|
| 3296 |
+
}
|
| 3297 |
+
|
| 3298 |
+
const avaliadorEscolhido = avaliadores.find((item) => item.nome_completo === avaliadorSelecionado) || null
|
| 3299 |
+
const elaboradorPayload = avaliadorEscolhido || elaborador || null
|
| 3300 |
+
const observacaoTexto = String(observacaoModelo || '').trim()
|
| 3301 |
+
|
| 3302 |
+
setLoading(true)
|
| 3303 |
+
setError('')
|
| 3304 |
+
setRepositorioSaveStatus('')
|
| 3305 |
+
try {
|
| 3306 |
+
const salvar = async (confirmarSubstituicao) => {
|
| 3307 |
+
const resp = await api.saveModelRepository(
|
| 3308 |
+
sessionId,
|
| 3309 |
+
nomeExport,
|
| 3310 |
+
elaboradorPayload,
|
| 3311 |
+
observacaoTexto,
|
| 3312 |
+
confirmarSubstituicao,
|
| 3313 |
+
)
|
| 3314 |
+
aplicarRespostaModelosRepositorio(resp)
|
| 3315 |
+
setRepositorioSaveStatus(resp?.status || 'Modelo salvo no repositorio.')
|
| 3316 |
+
}
|
| 3317 |
+
|
| 3318 |
+
try {
|
| 3319 |
+
await salvar(false)
|
| 3320 |
+
} catch (err) {
|
| 3321 |
+
const substituidos = parseSubstituidosErro(err)
|
| 3322 |
+
const erroDuplicado = Number(err?.status) === 409 && substituidos.length > 0
|
| 3323 |
+
if (!erroDuplicado) throw err
|
| 3324 |
+
|
| 3325 |
+
const nomes = substituidos.join(', ')
|
| 3326 |
+
const confirma = window.confirm(
|
| 3327 |
+
`Ja existe modelo com este nome no repositorio (${nomes}). Deseja sobrescrever?`,
|
| 3328 |
+
)
|
| 3329 |
+
if (!confirma) return
|
| 3330 |
+
await salvar(true)
|
| 3331 |
+
}
|
| 3332 |
+
} catch (err) {
|
| 3333 |
+
setError(err?.message || 'Falha ao salvar modelo no repositorio.')
|
| 3334 |
+
} finally {
|
| 3335 |
+
setLoading(false)
|
| 3336 |
+
}
|
| 3337 |
+
}
|
| 3338 |
+
|
| 3339 |
async function onExportBase() {
|
| 3340 |
if (!sessionId) return
|
| 3341 |
await withBusy(async () => {
|
|
|
|
| 3988 |
</div>
|
| 3989 |
</div>
|
| 3990 |
</div>
|
| 3991 |
+
{modeloCarregadoInfo.observacao_modelo ? (
|
| 3992 |
+
<div className="elaborador-badge modelo-observacao-badge">
|
| 3993 |
+
<div className="elaborador-badge-title">OBSERVAÇÃO</div>
|
| 3994 |
+
<div className="modelo-observacao-text">{modeloCarregadoInfo.observacao_modelo}</div>
|
| 3995 |
+
</div>
|
| 3996 |
+
) : null}
|
| 3997 |
</>
|
| 3998 |
) : null}
|
| 3999 |
</div>
|
|
|
|
| 5921 |
</SectionBlock>
|
| 5922 |
|
| 5923 |
<SectionBlock step="19" title="Exportar Modelo" subtitle="Geração do pacote .dai e download da base tratada.">
|
| 5924 |
+
<div className="export-model-layout">
|
| 5925 |
+
<div className="export-model-note-card">
|
| 5926 |
+
<div className="export-model-note-head">
|
| 5927 |
+
<label htmlFor="elaboracao-observacao-modelo" className="export-model-note-label">Observação do modelo</label>
|
| 5928 |
+
<p className="export-model-note-help">
|
| 5929 |
+
Esse texto será salvo e apresentado quando o modelo for visualizado.
|
| 5930 |
+
</p>
|
| 5931 |
+
</div>
|
| 5932 |
+
<textarea
|
| 5933 |
+
id="elaboracao-observacao-modelo"
|
| 5934 |
+
className="export-model-note-input"
|
| 5935 |
+
value={observacaoModelo}
|
| 5936 |
+
onChange={(e) => setObservacaoModelo(e.target.value)}
|
| 5937 |
+
placeholder="Informações relevantes que podem ser úteis ao usuário do modelo."
|
| 5938 |
+
rows={4}
|
| 5939 |
+
/>
|
| 5940 |
+
</div>
|
| 5941 |
+
|
| 5942 |
+
<div className="export-model-actions-card">
|
| 5943 |
+
<div className="export-model-fields">
|
| 5944 |
+
<label className="export-model-field">
|
| 5945 |
+
<span>Nome do arquivo (.dai)</span>
|
| 5946 |
+
<input
|
| 5947 |
+
type="text"
|
| 5948 |
+
value={nomeArquivoExport}
|
| 5949 |
+
onChange={(e) => setNomeArquivoExport(e.target.value)}
|
| 5950 |
+
placeholder="Digite o nome do arquivo"
|
| 5951 |
+
/>
|
| 5952 |
+
</label>
|
| 5953 |
+
|
| 5954 |
+
<label className="export-model-field">
|
| 5955 |
+
<span>Avaliador</span>
|
| 5956 |
+
<select value={avaliadorSelecionado} onChange={(e) => setAvaliadorSelecionado(e.target.value)}>
|
| 5957 |
+
<option value="">Manter elaborador do modelo (se houver)</option>
|
| 5958 |
+
{avaliadores.map((item) => (
|
| 5959 |
+
<option key={`aval-exp-${item.nome_completo}`} value={item.nome_completo}>
|
| 5960 |
+
{item.nome_completo}
|
| 5961 |
+
</option>
|
| 5962 |
+
))}
|
| 5963 |
+
</select>
|
| 5964 |
+
</label>
|
| 5965 |
+
</div>
|
| 5966 |
+
|
| 5967 |
+
<div className="export-model-buttons">
|
| 5968 |
+
<button
|
| 5969 |
+
type="button"
|
| 5970 |
+
className="export-model-btn-primary"
|
| 5971 |
+
onClick={onExportModel}
|
| 5972 |
+
disabled={loading || !String(nomeArquivoExport || '').trim()}
|
| 5973 |
+
>
|
| 5974 |
+
Exportar modelo
|
| 5975 |
+
</button>
|
| 5976 |
+
<button
|
| 5977 |
+
type="button"
|
| 5978 |
+
className="export-model-btn-repo"
|
| 5979 |
+
onClick={onSaveModelRepository}
|
| 5980 |
+
disabled={loading || !String(nomeArquivoExport || '').trim() || !repositorioSalvamentoDiretoHabilitado}
|
| 5981 |
+
title={
|
| 5982 |
+
!repositorioUploadAdminHabilitado
|
| 5983 |
+
? 'Disponivel apenas para administradores.'
|
| 5984 |
+
: !repositorioSalvamentoDiretoHabilitado
|
| 5985 |
+
? 'Disponivel apenas quando o repositorio usa HF Dataset.'
|
| 5986 |
+
: ''
|
| 5987 |
+
}
|
| 5988 |
+
>
|
| 5989 |
+
Salvar no repositorio
|
| 5990 |
+
</button>
|
| 5991 |
+
<button
|
| 5992 |
+
type="button"
|
| 5993 |
+
className="export-model-btn-base"
|
| 5994 |
+
onClick={onExportBase}
|
| 5995 |
+
disabled={loading}
|
| 5996 |
+
>
|
| 5997 |
+
Exportar base CSV
|
| 5998 |
+
</button>
|
| 5999 |
+
</div>
|
| 6000 |
+
</div>
|
| 6001 |
+
</div>
|
| 6002 |
+
<div className="section1-empty-hint export-model-hint">
|
| 6003 |
+
{repoModelosLoading
|
| 6004 |
+
? 'Verificando fonte do repositório...'
|
| 6005 |
+
: !repositorioUploadAdminHabilitado
|
| 6006 |
+
? 'Salvar no repositório fica disponível apenas para administradores.'
|
| 6007 |
+
: repositorioSalvamentoDiretoHabilitado
|
| 6008 |
+
? `Salvamento direto habilitado. ${repoFonteModelos || ''}`.trim()
|
| 6009 |
+
: 'Salvar no repositório fica desabilitado na versão local.'}
|
| 6010 |
</div>
|
| 6011 |
+
{repositorioSaveStatus ? <div className="section1-empty-hint export-model-status">{repositorioSaveStatus}</div> : null}
|
| 6012 |
</SectionBlock>
|
| 6013 |
</>
|
| 6014 |
) : null}
|
frontend/src/components/PesquisaTab.jsx
CHANGED
|
@@ -679,6 +679,7 @@ export default function PesquisaTab({ sessionId, onUsarModeloEmAvaliacao = null
|
|
| 679 |
setModeloAbertoMeta({
|
| 680 |
id: modelo.id,
|
| 681 |
nome: modelo.nome_modelo || modelo.arquivo || modelo.id,
|
|
|
|
| 682 |
})
|
| 683 |
window.requestAnimationFrame(() => {
|
| 684 |
scrollParaAbasGeraisNoTopo()
|
|
@@ -757,7 +758,7 @@ export default function PesquisaTab({ sessionId, onUsarModeloEmAvaliacao = null
|
|
| 757 |
<div className="pesquisa-opened-model-head">
|
| 758 |
<div className="pesquisa-opened-model-title-wrap">
|
| 759 |
<h3>{modeloAbertoMeta?.nome || 'Modelo'}</h3>
|
| 760 |
-
<p>
|
| 761 |
</div>
|
| 762 |
<button
|
| 763 |
type="button"
|
|
|
|
| 679 |
setModeloAbertoMeta({
|
| 680 |
id: modelo.id,
|
| 681 |
nome: modelo.nome_modelo || modelo.arquivo || modelo.id,
|
| 682 |
+
observacao: String(resp?.meta_modelo?.observacao_modelo || '').trim(),
|
| 683 |
})
|
| 684 |
window.requestAnimationFrame(() => {
|
| 685 |
scrollParaAbasGeraisNoTopo()
|
|
|
|
| 758 |
<div className="pesquisa-opened-model-head">
|
| 759 |
<div className="pesquisa-opened-model-title-wrap">
|
| 760 |
<h3>{modeloAbertoMeta?.nome || 'Modelo'}</h3>
|
| 761 |
+
{modeloAbertoMeta?.observacao ? <p>{modeloAbertoMeta.observacao}</p> : null}
|
| 762 |
</div>
|
| 763 |
<button
|
| 764 |
type="button"
|
frontend/src/components/RepositorioTab.jsx
CHANGED
|
@@ -210,6 +210,7 @@ export default function RepositorioTab({ authUser, sessionId }) {
|
|
| 210 |
setModeloAbertoMeta({
|
| 211 |
id: String(item?.id || ''),
|
| 212 |
nome: item?.nome_modelo || item?.arquivo || String(item?.id || ''),
|
|
|
|
| 213 |
})
|
| 214 |
} catch (err) {
|
| 215 |
setModeloAbertoError(err.message || 'Falha ao abrir modelo.')
|
|
@@ -256,7 +257,7 @@ export default function RepositorioTab({ authUser, sessionId }) {
|
|
| 256 |
<div className="pesquisa-opened-model-head">
|
| 257 |
<div className="pesquisa-opened-model-title-wrap">
|
| 258 |
<h3>{modeloAbertoMeta?.nome || 'Modelo'}</h3>
|
| 259 |
-
<p>
|
| 260 |
</div>
|
| 261 |
<button type="button" className="model-source-back-btn model-source-back-btn-danger" onClick={onVoltarRepositorio} disabled={modeloAbertoLoading}>
|
| 262 |
Voltar ao repositório
|
|
|
|
| 210 |
setModeloAbertoMeta({
|
| 211 |
id: String(item?.id || ''),
|
| 212 |
nome: item?.nome_modelo || item?.arquivo || String(item?.id || ''),
|
| 213 |
+
observacao: String(resp?.meta_modelo?.observacao_modelo || '').trim(),
|
| 214 |
})
|
| 215 |
} catch (err) {
|
| 216 |
setModeloAbertoError(err.message || 'Falha ao abrir modelo.')
|
|
|
|
| 257 |
<div className="pesquisa-opened-model-head">
|
| 258 |
<div className="pesquisa-opened-model-title-wrap">
|
| 259 |
<h3>{modeloAbertoMeta?.nome || 'Modelo'}</h3>
|
| 260 |
+
{modeloAbertoMeta?.observacao ? <p>{modeloAbertoMeta.observacao}</p> : null}
|
| 261 |
</div>
|
| 262 |
<button type="button" className="model-source-back-btn model-source-back-btn-danger" onClick={onVoltarRepositorio} disabled={modeloAbertoLoading}>
|
| 263 |
Voltar ao repositório
|
frontend/src/styles.css
CHANGED
|
@@ -2840,6 +2840,143 @@ button:disabled {
|
|
| 2840 |
color: #2a3b4d;
|
| 2841 |
}
|
| 2842 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2843 |
.section1-groups {
|
| 2844 |
display: grid;
|
| 2845 |
gap: 18px;
|
|
@@ -3172,6 +3309,18 @@ button.btn-upload-select {
|
|
| 3172 |
font-size: 0.83rem;
|
| 3173 |
}
|
| 3174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3175 |
.modelo-variaveis-box {
|
| 3176 |
padding: 9px 11px;
|
| 3177 |
border-radius: 11px;
|
|
|
|
| 2840 |
color: #2a3b4d;
|
| 2841 |
}
|
| 2842 |
|
| 2843 |
+
.export-model-layout {
|
| 2844 |
+
display: grid;
|
| 2845 |
+
gap: 14px;
|
| 2846 |
+
margin-bottom: 12px;
|
| 2847 |
+
}
|
| 2848 |
+
|
| 2849 |
+
.export-model-note-card,
|
| 2850 |
+
.export-model-actions-card {
|
| 2851 |
+
border: 1px solid #dde7f0;
|
| 2852 |
+
border-radius: 14px;
|
| 2853 |
+
background:
|
| 2854 |
+
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 251, 255, 0.98) 100%);
|
| 2855 |
+
box-shadow:
|
| 2856 |
+
0 10px 22px rgba(30, 44, 60, 0.05),
|
| 2857 |
+
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
| 2858 |
+
padding: 16px;
|
| 2859 |
+
}
|
| 2860 |
+
|
| 2861 |
+
.export-model-note-card {
|
| 2862 |
+
display: grid;
|
| 2863 |
+
gap: 12px;
|
| 2864 |
+
border-left: 4px solid #ffb259;
|
| 2865 |
+
}
|
| 2866 |
+
|
| 2867 |
+
.export-model-note-head {
|
| 2868 |
+
display: grid;
|
| 2869 |
+
gap: 4px;
|
| 2870 |
+
}
|
| 2871 |
+
|
| 2872 |
+
.export-model-note-label {
|
| 2873 |
+
margin: 0;
|
| 2874 |
+
font-family: 'Sora', sans-serif;
|
| 2875 |
+
font-size: 0.94rem;
|
| 2876 |
+
color: #243547;
|
| 2877 |
+
}
|
| 2878 |
+
|
| 2879 |
+
.export-model-note-help {
|
| 2880 |
+
margin: 0;
|
| 2881 |
+
color: #61778d;
|
| 2882 |
+
font-size: 0.84rem;
|
| 2883 |
+
line-height: 1.45;
|
| 2884 |
+
}
|
| 2885 |
+
|
| 2886 |
+
.export-model-note-input {
|
| 2887 |
+
min-height: 112px;
|
| 2888 |
+
resize: vertical;
|
| 2889 |
+
line-height: 1.5;
|
| 2890 |
+
padding: 12px 14px;
|
| 2891 |
+
background:
|
| 2892 |
+
linear-gradient(180deg, #ffffff 0%, #fdfefe 100%);
|
| 2893 |
+
border-color: #ccd9e5;
|
| 2894 |
+
box-shadow: inset 0 1px 2px rgba(25, 39, 53, 0.04);
|
| 2895 |
+
}
|
| 2896 |
+
|
| 2897 |
+
.export-model-actions-card {
|
| 2898 |
+
display: grid;
|
| 2899 |
+
gap: 14px;
|
| 2900 |
+
}
|
| 2901 |
+
|
| 2902 |
+
.export-model-fields {
|
| 2903 |
+
display: grid;
|
| 2904 |
+
grid-template-columns: minmax(240px, 1.1fr) minmax(260px, 1fr);
|
| 2905 |
+
gap: 14px;
|
| 2906 |
+
}
|
| 2907 |
+
|
| 2908 |
+
.export-model-field {
|
| 2909 |
+
display: grid;
|
| 2910 |
+
gap: 7px;
|
| 2911 |
+
min-width: 0;
|
| 2912 |
+
}
|
| 2913 |
+
|
| 2914 |
+
.export-model-field span {
|
| 2915 |
+
font-weight: 700;
|
| 2916 |
+
color: #394a5e;
|
| 2917 |
+
font-size: 0.88rem;
|
| 2918 |
+
}
|
| 2919 |
+
|
| 2920 |
+
.export-model-field input,
|
| 2921 |
+
.export-model-field select {
|
| 2922 |
+
width: 100%;
|
| 2923 |
+
}
|
| 2924 |
+
|
| 2925 |
+
.export-model-buttons {
|
| 2926 |
+
display: flex;
|
| 2927 |
+
flex-wrap: wrap;
|
| 2928 |
+
gap: 10px;
|
| 2929 |
+
}
|
| 2930 |
+
|
| 2931 |
+
.export-model-buttons button {
|
| 2932 |
+
min-height: 40px;
|
| 2933 |
+
padding-inline: 16px;
|
| 2934 |
+
}
|
| 2935 |
+
|
| 2936 |
+
.export-model-btn-primary {
|
| 2937 |
+
--btn-bg-start: #ff9b32;
|
| 2938 |
+
--btn-bg-end: #e67900;
|
| 2939 |
+
--btn-border: #d97300;
|
| 2940 |
+
--btn-shadow-soft: rgba(230, 121, 0, 0.18);
|
| 2941 |
+
--btn-shadow-strong: rgba(230, 121, 0, 0.28);
|
| 2942 |
+
}
|
| 2943 |
+
|
| 2944 |
+
.export-model-btn-repo {
|
| 2945 |
+
--btn-bg-start: #2ea94f;
|
| 2946 |
+
--btn-bg-end: #238a40;
|
| 2947 |
+
--btn-border: #1b7435;
|
| 2948 |
+
--btn-shadow-soft: rgba(35, 138, 64, 0.18);
|
| 2949 |
+
--btn-shadow-strong: rgba(35, 138, 64, 0.28);
|
| 2950 |
+
}
|
| 2951 |
+
|
| 2952 |
+
.export-model-btn-base {
|
| 2953 |
+
--btn-bg-start: #4e95cf;
|
| 2954 |
+
--btn-bg-end: #3d82be;
|
| 2955 |
+
--btn-border: #2e6d9f;
|
| 2956 |
+
--btn-shadow-soft: rgba(61, 130, 190, 0.18);
|
| 2957 |
+
--btn-shadow-strong: rgba(61, 130, 190, 0.28);
|
| 2958 |
+
}
|
| 2959 |
+
|
| 2960 |
+
.export-model-hint,
|
| 2961 |
+
.export-model-status {
|
| 2962 |
+
margin-top: 4px;
|
| 2963 |
+
}
|
| 2964 |
+
|
| 2965 |
+
.export-model-status {
|
| 2966 |
+
color: #1f5e3a;
|
| 2967 |
+
font-weight: 700;
|
| 2968 |
+
}
|
| 2969 |
+
|
| 2970 |
+
@media (max-width: 900px) {
|
| 2971 |
+
.export-model-fields {
|
| 2972 |
+
grid-template-columns: 1fr;
|
| 2973 |
+
}
|
| 2974 |
+
|
| 2975 |
+
.export-model-buttons button {
|
| 2976 |
+
flex: 1 1 100%;
|
| 2977 |
+
}
|
| 2978 |
+
}
|
| 2979 |
+
|
| 2980 |
.section1-groups {
|
| 2981 |
display: grid;
|
| 2982 |
gap: 18px;
|
|
|
|
| 3309 |
font-size: 0.83rem;
|
| 3310 |
}
|
| 3311 |
|
| 3312 |
+
.modelo-observacao-badge {
|
| 3313 |
+
margin-top: 10px;
|
| 3314 |
+
}
|
| 3315 |
+
|
| 3316 |
+
.modelo-observacao-text {
|
| 3317 |
+
margin-top: 6px;
|
| 3318 |
+
color: #40586f;
|
| 3319 |
+
font-size: 0.88rem;
|
| 3320 |
+
line-height: 1.52;
|
| 3321 |
+
white-space: pre-line;
|
| 3322 |
+
}
|
| 3323 |
+
|
| 3324 |
.modelo-variaveis-box {
|
| 3325 |
padding: 9px 11px;
|
| 3326 |
border-radius: 11px;
|