Update app.py
Browse files
app.py
CHANGED
|
@@ -149,20 +149,26 @@ def _set_query_params(new_params: dict):
|
|
| 149 |
except Exception:
|
| 150 |
pass
|
| 151 |
|
|
|
|
| 152 |
def _check_rerun_qs(pagina_atual: str = ""):
|
| 153 |
"""
|
| 154 |
Se a URL contiver rr=1 (ou true), força um rerun e limpa o parâmetro para evitar loop.
|
| 155 |
✅ Não dispara quando estiver na página 'resposta' (Inbox Admin).
|
| 156 |
-
✅ Consome apenas uma vez por sessão.
|
| 157 |
✅ (PATCH) Também não dispara quando estiver em 'outlook_relatorio' para não interromper leitura COM.
|
| 158 |
✅ (PATCH) Não dispara quando estiver em 'formulario'.
|
|
|
|
| 159 |
"""
|
| 160 |
try:
|
| 161 |
if st.session_state.get("__qs_rr_consumed__", False):
|
| 162 |
return
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
if pagina_atual in ("resposta", "outlook_relatorio", "formulario"):
|
| 165 |
-
return #
|
| 166 |
|
| 167 |
params = _get_query_params()
|
| 168 |
rr_raw = params.get("rr", ["0"])
|
|
@@ -415,6 +421,8 @@ def _render_aviso_global_topbar():
|
|
| 415 |
font_size = 14
|
| 416 |
|
| 417 |
altura = 52 # px
|
|
|
|
|
|
|
| 418 |
|
| 419 |
st.markdown(
|
| 420 |
f"""
|
|
@@ -428,9 +436,10 @@ header[data-testid="stHeader"],
|
|
| 428 |
.stApp [class*="stDialog"] {{
|
| 429 |
z-index: 1 !important;
|
| 430 |
}}
|
| 431 |
-
|
|
|
|
| 432 |
[data-testid="stAppViewContainer"] {{
|
| 433 |
-
padding-top: {
|
| 434 |
}}
|
| 435 |
|
| 436 |
.ag-topbar-wrap {{
|
|
@@ -445,7 +454,14 @@ header[data-testid="stHeader"],
|
|
| 445 |
box-shadow: 0 2px 8px rgba(0,0,0,.15);
|
| 446 |
border-radius: 0 0 10px 10px;
|
| 447 |
pointer-events: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
}}
|
|
|
|
| 449 |
.ag-topbar-inner {{
|
| 450 |
display: flex;
|
| 451 |
align-items: center;
|
|
@@ -456,16 +472,29 @@ header[data-testid="stHeader"],
|
|
| 456 |
font-size: {font_size}px;
|
| 457 |
letter-spacing: .2px;
|
| 458 |
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
}}
|
|
|
|
| 460 |
.ag-topbar-marquee > span {{
|
| 461 |
display: inline-block;
|
| 462 |
padding-left: 100%;
|
| 463 |
animation: ag-marquee {velocidade}s linear infinite;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
}}
|
|
|
|
| 465 |
@keyframes ag-marquee {{
|
| 466 |
0% {{ transform: translateX(0); }}
|
| 467 |
100% {{ transform: translateX(-100%); }}
|
| 468 |
}}
|
|
|
|
| 469 |
/* Acessibilidade: reduz movimento */
|
| 470 |
@media (prefers-reduced-motion: reduce) {{
|
| 471 |
.ag-topbar-marquee > span {{
|
|
@@ -473,18 +502,19 @@ header[data-testid="stHeader"],
|
|
| 473 |
padding-left: 0;
|
| 474 |
}}
|
| 475 |
}}
|
|
|
|
| 476 |
@media (max-width: 500px) {{
|
| 477 |
.ag-topbar-inner {{
|
| 478 |
font-size: {max(10, font_size-3)}px;
|
| 479 |
padding: 0 8px;
|
| 480 |
height: 44px;
|
| 481 |
}}
|
|
|
|
| 482 |
[data-testid="stAppViewContainer"] {{
|
| 483 |
-
padding-top:
|
| 484 |
}}
|
| 485 |
}}
|
| 486 |
</style>
|
| 487 |
-
|
| 488 |
<div class="ag-topbar-wrap">
|
| 489 |
<div class="ag-topbar-inner {'ag-topbar-marquee' if efeito=='marquee' else ''}">
|
| 490 |
<span>{aviso.mensagem}</span>
|
|
@@ -619,6 +649,27 @@ def _render_auth_diag_panel():
|
|
| 619 |
except Exception as e:
|
| 620 |
st.sidebar.error(f"Falha ao importar login.py: {e}")
|
| 621 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
# ===============================
|
| 623 |
# MAIN
|
| 624 |
# ===============================
|
|
@@ -640,7 +691,8 @@ def main():
|
|
| 640 |
# LOGIN
|
| 641 |
if not st.session_state.logado:
|
| 642 |
st.session_state.quiz_verificado = False
|
| 643 |
-
|
|
|
|
| 644 |
login()
|
| 645 |
|
| 646 |
# ⚠️ Opcional: dica breve quando nenhum caminho de auth está definido
|
|
@@ -859,7 +911,8 @@ def main():
|
|
| 859 |
# QUIZ
|
| 860 |
if not st.session_state.quiz_verificado:
|
| 861 |
if not quiz_respondido_hoje(usuario):
|
| 862 |
-
|
|
|
|
| 863 |
quiz.main()
|
| 864 |
return
|
| 865 |
else:
|
|
@@ -867,7 +920,8 @@ def main():
|
|
| 867 |
st.rerun()
|
| 868 |
|
| 869 |
# SISTEMA LIBERADO
|
| 870 |
-
|
|
|
|
| 871 |
_render_aviso_global_topbar()
|
| 872 |
_show_birthday_banner_if_needed()
|
| 873 |
|
|
@@ -1106,8 +1160,12 @@ def main():
|
|
| 1106 |
is_outlook_rel = (pagina_id == "outlook_relatorio")
|
| 1107 |
is_formulario = (pagina_id == "formulario")
|
| 1108 |
is_recebimento = (pagina_id == "recebimento")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1109 |
interval_sec = int(st.session_state.get("__auto_refresh_interval_sec__", 60))
|
| 1110 |
-
if (interval_sec > 0) and not (is_inbox_admin or is_outlook_rel or is_formulario or is_recebimento):
|
| 1111 |
st_autorefresh(interval=interval_sec * 1000, key=f"sidebar_autorefresh_{interval_sec}s")
|
| 1112 |
except Exception:
|
| 1113 |
pass
|
|
|
|
| 149 |
except Exception:
|
| 150 |
pass
|
| 151 |
|
| 152 |
+
# 🔒 ANTI-TREMOR PATCH: não consumir rr=1 quando login/quiz ainda não concluídos
|
| 153 |
def _check_rerun_qs(pagina_atual: str = ""):
|
| 154 |
"""
|
| 155 |
Se a URL contiver rr=1 (ou true), força um rerun e limpa o parâmetro para evitar loop.
|
| 156 |
✅ Não dispara quando estiver na página 'resposta' (Inbox Admin).
|
|
|
|
| 157 |
✅ (PATCH) Também não dispara quando estiver em 'outlook_relatorio' para não interromper leitura COM.
|
| 158 |
✅ (PATCH) Não dispara quando estiver em 'formulario'.
|
| 159 |
+
✅ (ANTI-TREMOR) Não dispara enquanto login/quiz não concluídos (para não mexer na logo).
|
| 160 |
"""
|
| 161 |
try:
|
| 162 |
if st.session_state.get("__qs_rr_consumed__", False):
|
| 163 |
return
|
| 164 |
+
|
| 165 |
+
# Evita rr=1 quando a tela está em login/quiz (logo mais evidente)
|
| 166 |
+
if (not st.session_state.get("logado")) or (not st.session_state.get("quiz_verificado")):
|
| 167 |
+
return
|
| 168 |
+
|
| 169 |
+
# Evita rr=1 em módulos sensíveis a rerun/refresh
|
| 170 |
if pagina_atual in ("resposta", "outlook_relatorio", "formulario"):
|
| 171 |
+
return # evita 'piscar' e cancelamentos
|
| 172 |
|
| 173 |
params = _get_query_params()
|
| 174 |
rr_raw = params.get("rr", ["0"])
|
|
|
|
| 421 |
font_size = 14
|
| 422 |
|
| 423 |
altura = 52 # px
|
| 424 |
+
# 🔒 ANTI-TREMOR PATCH: padding-top constante e levemente maior que a topbar
|
| 425 |
+
padding_top = max(60, altura + 8)
|
| 426 |
|
| 427 |
st.markdown(
|
| 428 |
f"""
|
|
|
|
| 436 |
.stApp [class*="stDialog"] {{
|
| 437 |
z-index: 1 !important;
|
| 438 |
}}
|
| 439 |
+
|
| 440 |
+
/* 🔒 ANTI-TREMOR: reserva fixa do topo para a barra */
|
| 441 |
[data-testid="stAppViewContainer"] {{
|
| 442 |
+
padding-top: {padding_top}px !important;
|
| 443 |
}}
|
| 444 |
|
| 445 |
.ag-topbar-wrap {{
|
|
|
|
| 454 |
box-shadow: 0 2px 8px rgba(0,0,0,.15);
|
| 455 |
border-radius: 0 0 10px 10px;
|
| 456 |
pointer-events: none;
|
| 457 |
+
|
| 458 |
+
/* 🔒 ANTI-TREMOR: compositor-only + isolamento */
|
| 459 |
+
will-change: transform, opacity;
|
| 460 |
+
contain: layout paint size;
|
| 461 |
+
backface-visibility: hidden;
|
| 462 |
+
transform: translateZ(0);
|
| 463 |
}}
|
| 464 |
+
|
| 465 |
.ag-topbar-inner {{
|
| 466 |
display: flex;
|
| 467 |
align-items: center;
|
|
|
|
| 472 |
font-size: {font_size}px;
|
| 473 |
letter-spacing: .2px;
|
| 474 |
white-space: nowrap;
|
| 475 |
+
|
| 476 |
+
/* 🔒 ANTI-TREMOR */
|
| 477 |
+
will-change: transform, opacity;
|
| 478 |
+
backface-visibility: hidden;
|
| 479 |
+
transform: translateZ(0);
|
| 480 |
}}
|
| 481 |
+
|
| 482 |
.ag-topbar-marquee > span {{
|
| 483 |
display: inline-block;
|
| 484 |
padding-left: 100%;
|
| 485 |
animation: ag-marquee {velocidade}s linear infinite;
|
| 486 |
+
|
| 487 |
+
/* 🔒 ANTI-TREMOR */
|
| 488 |
+
will-change: transform;
|
| 489 |
+
backface-visibility: hidden;
|
| 490 |
+
transform: translateZ(0);
|
| 491 |
}}
|
| 492 |
+
|
| 493 |
@keyframes ag-marquee {{
|
| 494 |
0% {{ transform: translateX(0); }}
|
| 495 |
100% {{ transform: translateX(-100%); }}
|
| 496 |
}}
|
| 497 |
+
|
| 498 |
/* Acessibilidade: reduz movimento */
|
| 499 |
@media (prefers-reduced-motion: reduce) {{
|
| 500 |
.ag-topbar-marquee > span {{
|
|
|
|
| 502 |
padding-left: 0;
|
| 503 |
}}
|
| 504 |
}}
|
| 505 |
+
|
| 506 |
@media (max-width: 500px) {{
|
| 507 |
.ag-topbar-inner {{
|
| 508 |
font-size: {max(10, font_size-3)}px;
|
| 509 |
padding: 0 8px;
|
| 510 |
height: 44px;
|
| 511 |
}}
|
| 512 |
+
/* 🔒 ANTI-TREMOR: manter reserva estável no mobile também */
|
| 513 |
[data-testid="stAppViewContainer"] {{
|
| 514 |
+
padding-top: {padding_top}px !important;
|
| 515 |
}}
|
| 516 |
}}
|
| 517 |
</style>
|
|
|
|
| 518 |
<div class="ag-topbar-wrap">
|
| 519 |
<div class="ag-topbar-inner {'ag-topbar-marquee' if efeito=='marquee' else ''}">
|
| 520 |
<span>{aviso.mensagem}</span>
|
|
|
|
| 649 |
except Exception as e:
|
| 650 |
st.sidebar.error(f"Falha ao importar login.py: {e}")
|
| 651 |
|
| 652 |
+
# ===============================
|
| 653 |
+
# 🔒 ANTI-TREMOR PATCH: wrapper idempotente para a LOGO
|
| 654 |
+
# ===============================
|
| 655 |
+
def exibir_logo_once(top: bool = False, sidebar: bool = False):
|
| 656 |
+
"""
|
| 657 |
+
Garante que a logo só seja inserida uma vez por posição (top e/ou sidebar) por sessão.
|
| 658 |
+
Se for chamada novamente, não reinjeta o HTML nem causa reflows.
|
| 659 |
+
"""
|
| 660 |
+
state = st.session_state.setdefault("__logo_once__", {"top": False, "sidebar": False})
|
| 661 |
+
need_top = bool(top and not state["top"])
|
| 662 |
+
need_sidebar = bool(sidebar and not state["sidebar"])
|
| 663 |
+
|
| 664 |
+
if need_top or need_sidebar:
|
| 665 |
+
try:
|
| 666 |
+
exibir_logo(top=need_top, sidebar=need_sidebar)
|
| 667 |
+
finally:
|
| 668 |
+
if need_top:
|
| 669 |
+
state["top"] = True
|
| 670 |
+
if need_sidebar:
|
| 671 |
+
state["sidebar"] = True
|
| 672 |
+
|
| 673 |
# ===============================
|
| 674 |
# MAIN
|
| 675 |
# ===============================
|
|
|
|
| 691 |
# LOGIN
|
| 692 |
if not st.session_state.logado:
|
| 693 |
st.session_state.quiz_verificado = False
|
| 694 |
+
# 🔒 ANTI-TREMOR: renderiza logo top apenas 1x
|
| 695 |
+
exibir_logo_once(top=True, sidebar=False)
|
| 696 |
login()
|
| 697 |
|
| 698 |
# ⚠️ Opcional: dica breve quando nenhum caminho de auth está definido
|
|
|
|
| 911 |
# QUIZ
|
| 912 |
if not st.session_state.quiz_verificado:
|
| 913 |
if not quiz_respondido_hoje(usuario):
|
| 914 |
+
# 🔒 ANTI-TREMOR: render 1x
|
| 915 |
+
exibir_logo_once(top=True, sidebar=False)
|
| 916 |
quiz.main()
|
| 917 |
return
|
| 918 |
else:
|
|
|
|
| 920 |
st.rerun()
|
| 921 |
|
| 922 |
# SISTEMA LIBERADO
|
| 923 |
+
# 🔒 ANTI-TREMOR: render 1x (topo + sidebar)
|
| 924 |
+
exibir_logo_once(top=True, sidebar=True)
|
| 925 |
_render_aviso_global_topbar()
|
| 926 |
_show_birthday_banner_if_needed()
|
| 927 |
|
|
|
|
| 1160 |
is_outlook_rel = (pagina_id == "outlook_relatorio")
|
| 1161 |
is_formulario = (pagina_id == "formulario")
|
| 1162 |
is_recebimento = (pagina_id == "recebimento")
|
| 1163 |
+
|
| 1164 |
+
# 🔒 ANTI-TREMOR: incluir telas com logo topo (login/quiz/primeira tela)
|
| 1165 |
+
is_login_or_quiz = (not st.session_state.get("logado")) or (not st.session_state.get("quiz_verificado"))
|
| 1166 |
+
|
| 1167 |
interval_sec = int(st.session_state.get("__auto_refresh_interval_sec__", 60))
|
| 1168 |
+
if (interval_sec > 0) and not (is_inbox_admin or is_outlook_rel or is_formulario or is_recebimento or is_login_or_quiz):
|
| 1169 |
st_autorefresh(interval=interval_sec * 1000, key=f"sidebar_autorefresh_{interval_sec}s")
|
| 1170 |
except Exception:
|
| 1171 |
pass
|