Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
3c854d0
1
Parent(s): 614e632
inclusao de menu na lateral esquerda
Browse files- frontend/src/App.jsx +72 -1
- frontend/src/components/ElaboracaoTab.jsx +278 -1
- frontend/src/styles.css +163 -0
frontend/src/App.jsx
CHANGED
|
@@ -38,6 +38,9 @@ export default function App() {
|
|
| 38 |
const [logsUsuario, setLogsUsuario] = useState('')
|
| 39 |
const [logsPage, setLogsPage] = useState(1)
|
| 40 |
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
|
|
|
|
|
|
|
|
| 41 |
const settingsMenuRef = useRef(null)
|
| 42 |
|
| 43 |
const isAdmin = String(authUser?.perfil || '').toLowerCase() === 'admin'
|
|
@@ -180,6 +183,51 @@ export default function App() {
|
|
| 180 |
}
|
| 181 |
}, [authUser])
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
async function onSubmitLogin(event) {
|
| 184 |
event.preventDefault()
|
| 185 |
setAuthError('')
|
|
@@ -275,9 +323,17 @@ export default function App() {
|
|
| 275 |
setShowStartupIntro(false)
|
| 276 |
}
|
| 277 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
return (
|
| 279 |
<div className="app-shell">
|
| 280 |
-
<header className={authUser ? 'app-header app-header-logged' : 'app-header app-header-logo-only'}>
|
| 281 |
<div className="brand-mark" aria-hidden="true">
|
| 282 |
<img src={`${import.meta.env.BASE_URL}logo_mesa.png`} alt="MESA" />
|
| 283 |
</div>
|
|
@@ -351,6 +407,21 @@ export default function App() {
|
|
| 351 |
) : null}
|
| 352 |
</header>
|
| 353 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
{authLoading ? <div className="status-line">Validando autenticação...</div> : null}
|
| 355 |
|
| 356 |
{!authLoading && !authUser ? (
|
|
|
|
| 38 |
const [logsUsuario, setLogsUsuario] = useState('')
|
| 39 |
const [logsPage, setLogsPage] = useState(1)
|
| 40 |
const [settingsOpen, setSettingsOpen] = useState(false)
|
| 41 |
+
const [showScrollHomeBtn, setShowScrollHomeBtn] = useState(false)
|
| 42 |
+
const [scrollHomeBtnLeft, setScrollHomeBtnLeft] = useState(8)
|
| 43 |
+
const headerRef = useRef(null)
|
| 44 |
const settingsMenuRef = useRef(null)
|
| 45 |
|
| 46 |
const isAdmin = String(authUser?.perfil || '').toLowerCase() === 'admin'
|
|
|
|
| 183 |
}
|
| 184 |
}, [authUser])
|
| 185 |
|
| 186 |
+
useEffect(() => {
|
| 187 |
+
if (typeof window === 'undefined') return undefined
|
| 188 |
+
if (!authUser) {
|
| 189 |
+
setShowScrollHomeBtn(false)
|
| 190 |
+
setScrollHomeBtnLeft(8)
|
| 191 |
+
return undefined
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
function resolveHomeButtonLeft() {
|
| 195 |
+
const buttonSize = 42
|
| 196 |
+
const navAnchor = document.querySelector('.elaboracao-side-nav-item')
|
| 197 |
+
if (navAnchor && typeof navAnchor.getBoundingClientRect === 'function') {
|
| 198 |
+
const rect = navAnchor.getBoundingClientRect()
|
| 199 |
+
return Math.max(8, rect.left + ((rect.width - buttonSize) / 2))
|
| 200 |
+
}
|
| 201 |
+
const shell = document.querySelector('.app-shell')
|
| 202 |
+
if (shell && typeof shell.getBoundingClientRect === 'function') {
|
| 203 |
+
const rect = shell.getBoundingClientRect()
|
| 204 |
+
return Math.max(8, rect.left)
|
| 205 |
+
}
|
| 206 |
+
return 8
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
function updateScrollHomeVisibility() {
|
| 210 |
+
const headerEl = headerRef.current
|
| 211 |
+
if (!headerEl) {
|
| 212 |
+
setShowScrollHomeBtn(false)
|
| 213 |
+
return
|
| 214 |
+
}
|
| 215 |
+
const rect = headerEl.getBoundingClientRect()
|
| 216 |
+
const shouldShow = rect.bottom <= 0
|
| 217 |
+
const nextLeft = resolveHomeButtonLeft()
|
| 218 |
+
setShowScrollHomeBtn((current) => (current === shouldShow ? current : shouldShow))
|
| 219 |
+
setScrollHomeBtnLeft((current) => (Math.abs(current - nextLeft) < 0.5 ? current : nextLeft))
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
updateScrollHomeVisibility()
|
| 223 |
+
window.addEventListener('scroll', updateScrollHomeVisibility, { passive: true })
|
| 224 |
+
window.addEventListener('resize', updateScrollHomeVisibility)
|
| 225 |
+
return () => {
|
| 226 |
+
window.removeEventListener('scroll', updateScrollHomeVisibility)
|
| 227 |
+
window.removeEventListener('resize', updateScrollHomeVisibility)
|
| 228 |
+
}
|
| 229 |
+
}, [authUser])
|
| 230 |
+
|
| 231 |
async function onSubmitLogin(event) {
|
| 232 |
event.preventDefault()
|
| 233 |
setAuthError('')
|
|
|
|
| 323 |
setShowStartupIntro(false)
|
| 324 |
}
|
| 325 |
|
| 326 |
+
function onScrollToHeader() {
|
| 327 |
+
if (typeof window === 'undefined') return
|
| 328 |
+
const headerEl = headerRef.current
|
| 329 |
+
if (!headerEl) return
|
| 330 |
+
const top = Math.max(0, window.scrollY + headerEl.getBoundingClientRect().top - 8)
|
| 331 |
+
window.scrollTo({ top, behavior: 'smooth' })
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
return (
|
| 335 |
<div className="app-shell">
|
| 336 |
+
<header ref={headerRef} className={authUser ? 'app-header app-header-logged' : 'app-header app-header-logo-only'}>
|
| 337 |
<div className="brand-mark" aria-hidden="true">
|
| 338 |
<img src={`${import.meta.env.BASE_URL}logo_mesa.png`} alt="MESA" />
|
| 339 |
</div>
|
|
|
|
| 407 |
) : null}
|
| 408 |
</header>
|
| 409 |
|
| 410 |
+
{authUser && showScrollHomeBtn ? (
|
| 411 |
+
<button
|
| 412 |
+
type="button"
|
| 413 |
+
className="scroll-home-btn"
|
| 414 |
+
style={{ left: `${scrollHomeBtnLeft}px` }}
|
| 415 |
+
onClick={onScrollToHeader}
|
| 416 |
+
aria-label="Voltar ao cabeçalho"
|
| 417 |
+
title="Voltar ao cabeçalho"
|
| 418 |
+
>
|
| 419 |
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
| 420 |
+
<path d="M11.49 2.26a.75.75 0 0 1 1.02 0l9 8.25a.75.75 0 0 1-1.02 1.1L20 11.12V20a2 2 0 0 1-2 2h-3.75a.75.75 0 0 1-.75-.75V16a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25v5.25a.75.75 0 0 1-.75.75H6a2 2 0 0 1-2-2v-8.88l-.49.49a.75.75 0 0 1-1.02-1.1l9-8.25Z" />
|
| 421 |
+
</svg>
|
| 422 |
+
</button>
|
| 423 |
+
) : null}
|
| 424 |
+
|
| 425 |
{authLoading ? <div className="status-line">Validando autenticação...</div> : null}
|
| 426 |
|
| 427 |
{!authLoading && !authUser ? (
|
frontend/src/components/ElaboracaoTab.jsx
CHANGED
|
@@ -37,6 +37,27 @@ const MAPA_RESIDUOS_EXTREMO_LIVRE = 'livre'
|
|
| 37 |
const MAPA_RESIDUOS_EXTREMO_ABS_DEFAULT = MAPA_RESIDUOS_EXTREMO_LIVRE
|
| 38 |
const MAPA_RESIDUOS_EXTREMO_ABS_OPTIONS = [2, 3, 4, 5, 6, 7, 8, 10]
|
| 39 |
const OUTLIER_RECURSIVO_TOOLTIP = 'Aplicar com recursividade executa os mesmos filtros em ciclos sucessivos: nos bastidores, simula a exclusão dos índices encontrados, recalcula o ajuste do modelo e as métricas de outlier e reaplica os filtros, repetindo até não surgir nenhum índice novo. Para você, o resultado prático é que o campo "A excluir" é preenchido automaticamente com o conjunto total de índices encontrados nessa simulação recursiva.'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
function grauBadgeClass(value) {
|
| 42 |
const grau = Number(value)
|
|
@@ -141,6 +162,22 @@ function parseIndicesFromText(value) {
|
|
| 141 |
return Array.from(indices)
|
| 142 |
}
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
function toFiniteNumber(value) {
|
| 145 |
if (typeof value === 'number') {
|
| 146 |
return Number.isFinite(value) ? value : null
|
|
@@ -837,6 +874,10 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 837 |
const elaboracaoRootRef = useRef(null)
|
| 838 |
const [disabledHint, setDisabledHint] = useState(null)
|
| 839 |
const [sectionsMountKey, setSectionsMountKey] = useState(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
|
| 841 |
const mapaChoices = useMemo(() => [MAPA_VARIAVEL_PADRAO, ...colunasNumericas], [colunasNumericas])
|
| 842 |
const mapaModoDisponivel = mapaVariavel !== MAPA_VARIAVEL_PADRAO
|
|
@@ -1248,6 +1289,8 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1248 |
),
|
| 1249 |
)
|
| 1250 |
const baseCarregada = Boolean(dados)
|
|
|
|
|
|
|
| 1251 |
|
| 1252 |
const hideDisabledHint = useCallback(() => {
|
| 1253 |
setDisabledHint(null)
|
|
@@ -1302,6 +1345,200 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1302 |
})
|
| 1303 |
}, [])
|
| 1304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1305 |
useEffect(() => {
|
| 1306 |
if (coordsInfo && !coordsInfo.tem_coords) {
|
| 1307 |
setCoordsMode('menu')
|
|
@@ -3055,9 +3292,48 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3055 |
setPercentuais([])
|
| 3056 |
}
|
| 3057 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3058 |
return (
|
| 3059 |
<div ref={elaboracaoRootRef} className="tab-content">
|
| 3060 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3061 |
<SectionBlock step="1" title="Importar Dados" subtitle="Upload de CSV, Excel ou .dai com recuperação do fluxo.">
|
| 3062 |
<div className="section1-groups">
|
| 3063 |
<div className="subpanel section1-group">
|
|
@@ -5139,6 +5415,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 5139 |
</SectionBlock>
|
| 5140 |
</>
|
| 5141 |
) : null}
|
|
|
|
| 5142 |
</div>
|
| 5143 |
|
| 5144 |
{disabledHint ? (
|
|
|
|
| 37 |
const MAPA_RESIDUOS_EXTREMO_ABS_DEFAULT = MAPA_RESIDUOS_EXTREMO_LIVRE
|
| 38 |
const MAPA_RESIDUOS_EXTREMO_ABS_OPTIONS = [2, 3, 4, 5, 6, 7, 8, 10]
|
| 39 |
const OUTLIER_RECURSIVO_TOOLTIP = 'Aplicar com recursividade executa os mesmos filtros em ciclos sucessivos: nos bastidores, simula a exclusão dos índices encontrados, recalcula o ajuste do modelo e as métricas de outlier e reaplica os filtros, repetindo até não surgir nenhum índice novo. Para você, o resultado prático é que o campo "A excluir" é preenchido automaticamente com o conjunto total de índices encontrados nessa simulação recursiva.'
|
| 40 |
+
const ELABORACAO_SECOES_NAV = [
|
| 41 |
+
{ step: '1', title: 'Importar Dados' },
|
| 42 |
+
{ step: '2', title: 'Resolver Coordenadas' },
|
| 43 |
+
{ step: '3', title: 'Visualizar Mapa dos Dados Importados' },
|
| 44 |
+
{ step: '4', title: 'Aplicar Filtros' },
|
| 45 |
+
{ step: '5', title: 'Selecionar Data de Mercado' },
|
| 46 |
+
{ step: '6', title: 'Selecionar Variável Dependente' },
|
| 47 |
+
{ step: '7', title: 'Selecionar Variáveis Independentes' },
|
| 48 |
+
{ step: '8', title: 'Estatísticas das Variáveis Selecionadas' },
|
| 49 |
+
{ step: '9', title: 'Teste de Micronumerosidade' },
|
| 50 |
+
{ step: '10', title: 'Gráficos de Dispersão das Variáveis Independentes' },
|
| 51 |
+
{ step: '11', title: 'Transformações Sugeridas' },
|
| 52 |
+
{ step: '12', title: 'Aplicação das Transformações' },
|
| 53 |
+
{ step: '13', title: 'Visualizar Mapa dos Dados de Mercado' },
|
| 54 |
+
{ step: '14', title: 'Diagnóstico de Modelo' },
|
| 55 |
+
{ step: '15', title: 'Gráficos de Diagnóstico do Modelo' },
|
| 56 |
+
{ step: '16', title: 'Analisar Resíduos' },
|
| 57 |
+
{ step: '17', title: 'Exclusão ou Reinclusão de Outliers' },
|
| 58 |
+
{ step: '18', title: 'Avaliação de Imóvel' },
|
| 59 |
+
{ step: '19', title: 'Exportar Modelo' },
|
| 60 |
+
]
|
| 61 |
|
| 62 |
function grauBadgeClass(value) {
|
| 63 |
const grau = Number(value)
|
|
|
|
| 162 |
return Array.from(indices)
|
| 163 |
}
|
| 164 |
|
| 165 |
+
function normalizeSectionSteps(values) {
|
| 166 |
+
if (!Array.isArray(values)) return []
|
| 167 |
+
return [...new Set(values.map((item) => String(item || '').trim()).filter(Boolean))]
|
| 168 |
+
.sort((a, b) => Number(a) - Number(b))
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
function isSameSectionStepList(currentList, nextList) {
|
| 172 |
+
if (currentList === nextList) return true
|
| 173 |
+
if (!Array.isArray(currentList) || !Array.isArray(nextList)) return false
|
| 174 |
+
if (currentList.length !== nextList.length) return false
|
| 175 |
+
for (let index = 0; index < currentList.length; index += 1) {
|
| 176 |
+
if (currentList[index] !== nextList[index]) return false
|
| 177 |
+
}
|
| 178 |
+
return true
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
function toFiniteNumber(value) {
|
| 182 |
if (typeof value === 'number') {
|
| 183 |
return Number.isFinite(value) ? value : null
|
|
|
|
| 874 |
const elaboracaoRootRef = useRef(null)
|
| 875 |
const [disabledHint, setDisabledHint] = useState(null)
|
| 876 |
const [sectionsMountKey, setSectionsMountKey] = useState(0)
|
| 877 |
+
const [renderedSectionSteps, setRenderedSectionSteps] = useState(() => ['1'])
|
| 878 |
+
const [visibleSectionSteps, setVisibleSectionSteps] = useState(() => ['1'])
|
| 879 |
+
const visibleSectionStepsRef = useRef(new Set(['1']))
|
| 880 |
+
const [sideNavDynamicStyle, setSideNavDynamicStyle] = useState({})
|
| 881 |
|
| 882 |
const mapaChoices = useMemo(() => [MAPA_VARIAVEL_PADRAO, ...colunasNumericas], [colunasNumericas])
|
| 883 |
const mapaModoDisponivel = mapaVariavel !== MAPA_VARIAVEL_PADRAO
|
|
|
|
| 1289 |
),
|
| 1290 |
)
|
| 1291 |
const baseCarregada = Boolean(dados)
|
| 1292 |
+
const renderedSectionStepsSet = useMemo(() => new Set(renderedSectionSteps), [renderedSectionSteps])
|
| 1293 |
+
const visibleSectionStepsSet = useMemo(() => new Set(visibleSectionSteps), [visibleSectionSteps])
|
| 1294 |
|
| 1295 |
const hideDisabledHint = useCallback(() => {
|
| 1296 |
setDisabledHint(null)
|
|
|
|
| 1345 |
})
|
| 1346 |
}, [])
|
| 1347 |
|
| 1348 |
+
useEffect(() => {
|
| 1349 |
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
| 1350 |
+
setRenderedSectionSteps(['1'])
|
| 1351 |
+
setVisibleSectionSteps(['1'])
|
| 1352 |
+
visibleSectionStepsRef.current = new Set(['1'])
|
| 1353 |
+
return undefined
|
| 1354 |
+
}
|
| 1355 |
+
|
| 1356 |
+
const root = elaboracaoRootRef.current || document
|
| 1357 |
+
const sections = Array.from(root.querySelectorAll('.workflow-section[data-section-step]'))
|
| 1358 |
+
const renderedSteps = normalizeSectionSteps(sections.map((section) => section.getAttribute('data-section-step')))
|
| 1359 |
+
const nextRenderedSteps = renderedSteps.length > 0 ? renderedSteps : ['1']
|
| 1360 |
+
|
| 1361 |
+
setRenderedSectionSteps((current) => (isSameSectionStepList(current, nextRenderedSteps) ? current : nextRenderedSteps))
|
| 1362 |
+
|
| 1363 |
+
if (sections.length === 0) {
|
| 1364 |
+
setVisibleSectionSteps(['1'])
|
| 1365 |
+
visibleSectionStepsRef.current = new Set(['1'])
|
| 1366 |
+
return undefined
|
| 1367 |
+
}
|
| 1368 |
+
|
| 1369 |
+
const getClosestStepToTop = () => {
|
| 1370 |
+
let closestStep = nextRenderedSteps[0] || '1'
|
| 1371 |
+
let closestDistance = Number.POSITIVE_INFINITY
|
| 1372 |
+
const topOffset = 96
|
| 1373 |
+
sections.forEach((section) => {
|
| 1374 |
+
const step = String(section.getAttribute('data-section-step') || '').trim()
|
| 1375 |
+
if (!step) return
|
| 1376 |
+
const rect = section.getBoundingClientRect()
|
| 1377 |
+
const distance = Math.abs(rect.top - topOffset)
|
| 1378 |
+
if (distance < closestDistance) {
|
| 1379 |
+
closestDistance = distance
|
| 1380 |
+
closestStep = step
|
| 1381 |
+
}
|
| 1382 |
+
})
|
| 1383 |
+
return closestStep
|
| 1384 |
+
}
|
| 1385 |
+
|
| 1386 |
+
const addLastStepWhenNearBottom = (steps) => {
|
| 1387 |
+
const normalized = normalizeSectionSteps(steps)
|
| 1388 |
+
const maxScrollY = Math.max(0, document.documentElement.scrollHeight - window.innerHeight)
|
| 1389 |
+
const nearBottom = maxScrollY > 0 && window.scrollY >= maxScrollY - 2
|
| 1390 |
+
if (!nearBottom) return normalized
|
| 1391 |
+
const lastStep = nextRenderedSteps[nextRenderedSteps.length - 1]
|
| 1392 |
+
if (!lastStep) return normalized
|
| 1393 |
+
return normalizeSectionSteps([...normalized, lastStep])
|
| 1394 |
+
}
|
| 1395 |
+
|
| 1396 |
+
const computeVisibleSteps = () => {
|
| 1397 |
+
const topOffset = 96
|
| 1398 |
+
const viewportBottom = window.innerHeight || document.documentElement.clientHeight || 0
|
| 1399 |
+
const nextVisible = addLastStepWhenNearBottom(
|
| 1400 |
+
sections
|
| 1401 |
+
.filter((section) => {
|
| 1402 |
+
const rect = section.getBoundingClientRect()
|
| 1403 |
+
return rect.bottom > topOffset && rect.top < viewportBottom
|
| 1404 |
+
})
|
| 1405 |
+
.map((section) => section.getAttribute('data-section-step')),
|
| 1406 |
+
)
|
| 1407 |
+
const normalizedVisible = nextVisible.length > 0 ? nextVisible : [getClosestStepToTop()]
|
| 1408 |
+
visibleSectionStepsRef.current = new Set(normalizedVisible)
|
| 1409 |
+
setVisibleSectionSteps((current) => (isSameSectionStepList(current, normalizedVisible) ? current : normalizedVisible))
|
| 1410 |
+
}
|
| 1411 |
+
|
| 1412 |
+
computeVisibleSteps()
|
| 1413 |
+
|
| 1414 |
+
const observer = new IntersectionObserver(
|
| 1415 |
+
() => {
|
| 1416 |
+
computeVisibleSteps()
|
| 1417 |
+
},
|
| 1418 |
+
{
|
| 1419 |
+
root: null,
|
| 1420 |
+
rootMargin: '-96px 0px 0px 0px',
|
| 1421 |
+
threshold: [0, 0.05, 0.2, 0.45, 0.7],
|
| 1422 |
+
},
|
| 1423 |
+
)
|
| 1424 |
+
|
| 1425 |
+
sections.forEach((section) => observer.observe(section))
|
| 1426 |
+
|
| 1427 |
+
let frameId = 0
|
| 1428 |
+
const onScroll = () => {
|
| 1429 |
+
if (frameId) return
|
| 1430 |
+
frameId = window.requestAnimationFrame(() => {
|
| 1431 |
+
frameId = 0
|
| 1432 |
+
computeVisibleSteps()
|
| 1433 |
+
})
|
| 1434 |
+
}
|
| 1435 |
+
|
| 1436 |
+
const onResize = () => {
|
| 1437 |
+
computeVisibleSteps()
|
| 1438 |
+
}
|
| 1439 |
+
|
| 1440 |
+
window.addEventListener('scroll', onScroll, { passive: true })
|
| 1441 |
+
window.addEventListener('resize', onResize)
|
| 1442 |
+
|
| 1443 |
+
return () => {
|
| 1444 |
+
window.removeEventListener('scroll', onScroll)
|
| 1445 |
+
window.removeEventListener('resize', onResize)
|
| 1446 |
+
if (frameId) {
|
| 1447 |
+
window.cancelAnimationFrame(frameId)
|
| 1448 |
+
}
|
| 1449 |
+
observer.disconnect()
|
| 1450 |
+
}
|
| 1451 |
+
}, [sectionsMountKey, baseCarregada])
|
| 1452 |
+
|
| 1453 |
+
useEffect(() => {
|
| 1454 |
+
if (typeof window === 'undefined') return undefined
|
| 1455 |
+
|
| 1456 |
+
const totalSections = ELABORACAO_SECOES_NAV.length
|
| 1457 |
+
let frameId = 0
|
| 1458 |
+
|
| 1459 |
+
const applyMetrics = () => {
|
| 1460 |
+
if (window.innerWidth <= 760) {
|
| 1461 |
+
setSideNavDynamicStyle((current) => (Object.keys(current || {}).length === 0 ? current : {}))
|
| 1462 |
+
return
|
| 1463 |
+
}
|
| 1464 |
+
|
| 1465 |
+
const topOffset = 96
|
| 1466 |
+
const bottomOffset = 14
|
| 1467 |
+
const availableHeight = Math.max(120, window.innerHeight - topOffset - bottomOffset)
|
| 1468 |
+
const maxSize = 34
|
| 1469 |
+
const minSize = 8
|
| 1470 |
+
const maxGap = 8
|
| 1471 |
+
const minGap = 0
|
| 1472 |
+
const totalGapSlots = Math.max(0, totalSections - 1)
|
| 1473 |
+
|
| 1474 |
+
let size = Math.floor((availableHeight - (minGap * totalGapSlots)) / totalSections)
|
| 1475 |
+
if (!Number.isFinite(size)) size = maxSize
|
| 1476 |
+
size = Math.max(minSize, Math.min(maxSize, size))
|
| 1477 |
+
|
| 1478 |
+
let gap = minGap
|
| 1479 |
+
let usedHeight = (size * totalSections) + (gap * totalGapSlots)
|
| 1480 |
+
let remainingHeight = availableHeight - usedHeight
|
| 1481 |
+
|
| 1482 |
+
if (remainingHeight > 0 && totalGapSlots > 0) {
|
| 1483 |
+
const extraGap = Math.min(maxGap - gap, Math.floor(remainingHeight / totalGapSlots))
|
| 1484 |
+
gap += Math.max(0, extraGap)
|
| 1485 |
+
usedHeight = (size * totalSections) + (gap * totalGapSlots)
|
| 1486 |
+
remainingHeight = availableHeight - usedHeight
|
| 1487 |
+
}
|
| 1488 |
+
|
| 1489 |
+
if (remainingHeight > 0) {
|
| 1490 |
+
const extraSize = Math.min(maxSize - size, Math.floor(remainingHeight / totalSections))
|
| 1491 |
+
size += Math.max(0, extraSize)
|
| 1492 |
+
}
|
| 1493 |
+
|
| 1494 |
+
while ((size * totalSections) + (gap * totalGapSlots) > availableHeight && (gap > minGap || size > minSize)) {
|
| 1495 |
+
if (gap > minGap) {
|
| 1496 |
+
gap -= 1
|
| 1497 |
+
} else {
|
| 1498 |
+
size -= 1
|
| 1499 |
+
}
|
| 1500 |
+
}
|
| 1501 |
+
|
| 1502 |
+
const numericFontSize = Math.max(8, Math.round(size * 0.37))
|
| 1503 |
+
const labelFontSize = Math.max(10, Math.round(size * 0.34))
|
| 1504 |
+
const labelOffset = Math.max(8, Math.round(size * 0.3))
|
| 1505 |
+
const nextStyle = {
|
| 1506 |
+
'--elab-nav-size': `${size}px`,
|
| 1507 |
+
'--elab-nav-gap': `${gap}px`,
|
| 1508 |
+
'--elab-nav-font-size': `${numericFontSize}px`,
|
| 1509 |
+
'--elab-nav-label-font-size': `${labelFontSize}px`,
|
| 1510 |
+
'--elab-nav-label-offset': `${labelOffset}px`,
|
| 1511 |
+
}
|
| 1512 |
+
|
| 1513 |
+
setSideNavDynamicStyle((current) => {
|
| 1514 |
+
const currentStyle = current || {}
|
| 1515 |
+
const currentKeys = Object.keys(currentStyle)
|
| 1516 |
+
const nextKeys = Object.keys(nextStyle)
|
| 1517 |
+
const unchanged = currentKeys.length === nextKeys.length && nextKeys.every((key) => currentStyle[key] === nextStyle[key])
|
| 1518 |
+
return unchanged ? currentStyle : nextStyle
|
| 1519 |
+
})
|
| 1520 |
+
}
|
| 1521 |
+
|
| 1522 |
+
const onResize = () => {
|
| 1523 |
+
if (frameId) {
|
| 1524 |
+
window.cancelAnimationFrame(frameId)
|
| 1525 |
+
}
|
| 1526 |
+
frameId = window.requestAnimationFrame(() => {
|
| 1527 |
+
frameId = 0
|
| 1528 |
+
applyMetrics()
|
| 1529 |
+
})
|
| 1530 |
+
}
|
| 1531 |
+
|
| 1532 |
+
applyMetrics()
|
| 1533 |
+
window.addEventListener('resize', onResize)
|
| 1534 |
+
return () => {
|
| 1535 |
+
window.removeEventListener('resize', onResize)
|
| 1536 |
+
if (frameId) {
|
| 1537 |
+
window.cancelAnimationFrame(frameId)
|
| 1538 |
+
}
|
| 1539 |
+
}
|
| 1540 |
+
}, [])
|
| 1541 |
+
|
| 1542 |
useEffect(() => {
|
| 1543 |
if (coordsInfo && !coordsInfo.tem_coords) {
|
| 1544 |
setCoordsMode('menu')
|
|
|
|
| 3292 |
setPercentuais([])
|
| 3293 |
}
|
| 3294 |
|
| 3295 |
+
function onScrollToSecao(step) {
|
| 3296 |
+
if (typeof window === 'undefined' || typeof document === 'undefined') return
|
| 3297 |
+
const targetStep = String(step || '').trim()
|
| 3298 |
+
if (!targetStep) return
|
| 3299 |
+
|
| 3300 |
+
const root = elaboracaoRootRef.current || document
|
| 3301 |
+
const secao = root.querySelector(`.workflow-section[data-section-step="${targetStep}"]`)
|
| 3302 |
+
if (!secao) return
|
| 3303 |
+
|
| 3304 |
+
const offsetTopo = 96
|
| 3305 |
+
const alvo = Math.max(0, window.scrollY + secao.getBoundingClientRect().top - offsetTopo)
|
| 3306 |
+
window.scrollTo({ top: alvo, behavior: 'smooth' })
|
| 3307 |
+
}
|
| 3308 |
+
|
| 3309 |
return (
|
| 3310 |
<div ref={elaboracaoRootRef} className="tab-content">
|
| 3311 |
+
<div className="elaboracao-layout" style={sideNavDynamicStyle}>
|
| 3312 |
+
<aside className="elaboracao-side-nav" aria-label="Navegação de seções da elaboração">
|
| 3313 |
+
<ol className="elaboracao-side-nav-list">
|
| 3314 |
+
{ELABORACAO_SECOES_NAV.map((secao) => {
|
| 3315 |
+
const rendered = renderedSectionStepsSet.has(secao.step)
|
| 3316 |
+
const visible = visibleSectionStepsSet.has(secao.step)
|
| 3317 |
+
return (
|
| 3318 |
+
<li key={`elab-nav-${secao.step}`}>
|
| 3319 |
+
<button
|
| 3320 |
+
type="button"
|
| 3321 |
+
className={`elaboracao-side-nav-item${visible ? ' is-active' : ''}${rendered ? '' : ' is-unavailable'}`}
|
| 3322 |
+
onClick={() => onScrollToSecao(secao.step)}
|
| 3323 |
+
title={`${secao.step} - ${secao.title}`}
|
| 3324 |
+
aria-current={visible ? 'true' : undefined}
|
| 3325 |
+
aria-label={`Seção ${secao.step}: ${secao.title}`}
|
| 3326 |
+
>
|
| 3327 |
+
<span className="elaboracao-side-nav-index" aria-hidden="true">{secao.step}</span>
|
| 3328 |
+
<span className="elaboracao-side-nav-label" aria-hidden="true">{secao.title}</span>
|
| 3329 |
+
</button>
|
| 3330 |
+
</li>
|
| 3331 |
+
)
|
| 3332 |
+
})}
|
| 3333 |
+
</ol>
|
| 3334 |
+
</aside>
|
| 3335 |
+
|
| 3336 |
+
<div key={`sections-${sectionsMountKey}`} className="workflow-sections-stack elaboracao-sections-stack">
|
| 3337 |
<SectionBlock step="1" title="Importar Dados" subtitle="Upload de CSV, Excel ou .dai com recuperação do fluxo.">
|
| 3338 |
<div className="section1-groups">
|
| 3339 |
<div className="subpanel section1-group">
|
|
|
|
| 5415 |
</SectionBlock>
|
| 5416 |
</>
|
| 5417 |
) : null}
|
| 5418 |
+
</div>
|
| 5419 |
</div>
|
| 5420 |
|
| 5421 |
{disabledHint ? (
|
frontend/src/styles.css
CHANGED
|
@@ -139,6 +139,35 @@ textarea {
|
|
| 139 |
font-size: 1.02rem;
|
| 140 |
}
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
.session-id {
|
| 143 |
display: inline-block;
|
| 144 |
padding: 5px 10px;
|
|
@@ -816,12 +845,118 @@ textarea {
|
|
| 816 |
color: var(--ok);
|
| 817 |
}
|
| 818 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
.workflow-section {
|
| 820 |
border: 2px solid #aebfd0;
|
| 821 |
border-radius: var(--radius-lg);
|
| 822 |
background: var(--bg-2);
|
| 823 |
min-width: 0;
|
| 824 |
max-width: 100%;
|
|
|
|
| 825 |
box-shadow:
|
| 826 |
0 8px 22px rgba(20, 28, 36, 0.1),
|
| 827 |
inset 0 0 0 1px #dfe9f3;
|
|
@@ -4555,6 +4690,34 @@ button.btn-download-subtle {
|
|
| 4555 |
}
|
| 4556 |
|
| 4557 |
@media (max-width: 760px) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4558 |
.tabs {
|
| 4559 |
grid-template-columns: 1fr;
|
| 4560 |
}
|
|
|
|
| 139 |
font-size: 1.02rem;
|
| 140 |
}
|
| 141 |
|
| 142 |
+
.scroll-home-btn {
|
| 143 |
+
position: fixed;
|
| 144 |
+
top: 8px;
|
| 145 |
+
left: 8px;
|
| 146 |
+
width: 42px;
|
| 147 |
+
height: 42px;
|
| 148 |
+
border-radius: 999px;
|
| 149 |
+
border: 1px solid #c96c00;
|
| 150 |
+
background: linear-gradient(180deg, #ffb350 0%, #e67900 100%);
|
| 151 |
+
color: #ffffff;
|
| 152 |
+
display: inline-flex;
|
| 153 |
+
align-items: center;
|
| 154 |
+
justify-content: center;
|
| 155 |
+
box-shadow:
|
| 156 |
+
0 8px 18px rgba(122, 64, 0, 0.36),
|
| 157 |
+
inset 0 0 0 1px rgba(255, 255, 255, 0.24);
|
| 158 |
+
z-index: 120;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.scroll-home-btn:hover {
|
| 162 |
+
transform: translateY(-1px) scale(1.04);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.scroll-home-btn svg {
|
| 166 |
+
width: 19px;
|
| 167 |
+
height: 19px;
|
| 168 |
+
fill: currentColor;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
.session-id {
|
| 172 |
display: inline-block;
|
| 173 |
padding: 5px 10px;
|
|
|
|
| 845 |
color: var(--ok);
|
| 846 |
}
|
| 847 |
|
| 848 |
+
.elaboracao-layout {
|
| 849 |
+
display: grid;
|
| 850 |
+
grid-template-columns: calc(var(--elab-nav-size, 34px) + 8px) minmax(0, 1fr);
|
| 851 |
+
align-items: start;
|
| 852 |
+
gap: 14px;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
.elaboracao-side-nav {
|
| 856 |
+
position: sticky;
|
| 857 |
+
top: 96px;
|
| 858 |
+
align-self: start;
|
| 859 |
+
z-index: 4;
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
.elaboracao-side-nav-list {
|
| 863 |
+
list-style: none;
|
| 864 |
+
margin: 0;
|
| 865 |
+
padding: 0;
|
| 866 |
+
display: flex;
|
| 867 |
+
flex-direction: column;
|
| 868 |
+
gap: var(--elab-nav-gap, 8px);
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
.elaboracao-side-nav-item {
|
| 872 |
+
position: relative;
|
| 873 |
+
width: var(--elab-nav-size, 34px);
|
| 874 |
+
height: var(--elab-nav-size, 34px);
|
| 875 |
+
padding: 0;
|
| 876 |
+
border-radius: 999px;
|
| 877 |
+
border: 1px solid #c3d2e1;
|
| 878 |
+
background: linear-gradient(180deg, #f8fbff 0%, #edf4fa 100%);
|
| 879 |
+
color: #5b7188;
|
| 880 |
+
display: inline-flex;
|
| 881 |
+
align-items: center;
|
| 882 |
+
justify-content: center;
|
| 883 |
+
font-family: 'Sora', sans-serif;
|
| 884 |
+
font-size: var(--elab-nav-font-size, 12px);
|
| 885 |
+
font-weight: 700;
|
| 886 |
+
box-shadow: 0 4px 10px rgba(23, 37, 50, 0.14);
|
| 887 |
+
transition: transform 0.16s ease, border-color 0.16s ease, background 0.16s ease, color 0.16s ease, box-shadow 0.16s ease;
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
.elaboracao-side-nav-item .elaboracao-side-nav-index {
|
| 891 |
+
line-height: 1;
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
.elaboracao-side-nav-item.is-active {
|
| 895 |
+
border-color: #bf6500;
|
| 896 |
+
background: linear-gradient(180deg, #ff9f31 0%, #e67900 100%);
|
| 897 |
+
color: #ffffff;
|
| 898 |
+
box-shadow:
|
| 899 |
+
0 6px 14px rgba(230, 121, 0, 0.34),
|
| 900 |
+
inset 0 0 0 1px rgba(255, 255, 255, 0.25);
|
| 901 |
+
}
|
| 902 |
+
|
| 903 |
+
.elaboracao-side-nav-item.is-unavailable {
|
| 904 |
+
opacity: 0.58;
|
| 905 |
+
border-style: dashed;
|
| 906 |
+
}
|
| 907 |
+
|
| 908 |
+
.elaboracao-side-nav-item:hover,
|
| 909 |
+
.elaboracao-side-nav-item:focus-visible {
|
| 910 |
+
transform: translateX(3px) scale(1.24);
|
| 911 |
+
border-color: #d06f00;
|
| 912 |
+
color: #6c3900;
|
| 913 |
+
z-index: 2;
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
.elaboracao-side-nav-item.is-active:hover,
|
| 917 |
+
.elaboracao-side-nav-item.is-active:focus-visible {
|
| 918 |
+
color: #ffffff;
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
.elaboracao-side-nav-label {
|
| 922 |
+
position: absolute;
|
| 923 |
+
left: calc(100% + var(--elab-nav-label-offset, 10px));
|
| 924 |
+
top: 50%;
|
| 925 |
+
transform: translate(-14px, -50%) scale(0.92);
|
| 926 |
+
transform-origin: left center;
|
| 927 |
+
opacity: 0;
|
| 928 |
+
pointer-events: none;
|
| 929 |
+
white-space: nowrap;
|
| 930 |
+
border-radius: 999px;
|
| 931 |
+
border: 1px solid #cf6f00;
|
| 932 |
+
background: linear-gradient(90deg, #fff4e3 0%, #ffe2bb 100%);
|
| 933 |
+
color: #7a3f00;
|
| 934 |
+
box-shadow: 0 10px 24px rgba(112, 62, 9, 0.24);
|
| 935 |
+
padding: 6px 11px;
|
| 936 |
+
font-family: 'Sora', sans-serif;
|
| 937 |
+
font-size: var(--elab-nav-label-font-size, 11px);
|
| 938 |
+
font-weight: 700;
|
| 939 |
+
letter-spacing: 0.01em;
|
| 940 |
+
transition: opacity 0.18s ease, transform 0.18s ease;
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
.elaboracao-side-nav-item:hover .elaboracao-side-nav-label,
|
| 944 |
+
.elaboracao-side-nav-item:focus-visible .elaboracao-side-nav-label {
|
| 945 |
+
opacity: 1;
|
| 946 |
+
transform: translate(0, -50%) scale(1.24);
|
| 947 |
+
}
|
| 948 |
+
|
| 949 |
+
.elaboracao-sections-stack {
|
| 950 |
+
min-width: 0;
|
| 951 |
+
}
|
| 952 |
+
|
| 953 |
.workflow-section {
|
| 954 |
border: 2px solid #aebfd0;
|
| 955 |
border-radius: var(--radius-lg);
|
| 956 |
background: var(--bg-2);
|
| 957 |
min-width: 0;
|
| 958 |
max-width: 100%;
|
| 959 |
+
scroll-margin-top: 102px;
|
| 960 |
box-shadow:
|
| 961 |
0 8px 22px rgba(20, 28, 36, 0.1),
|
| 962 |
inset 0 0 0 1px #dfe9f3;
|
|
|
|
| 4690 |
}
|
| 4691 |
|
| 4692 |
@media (max-width: 760px) {
|
| 4693 |
+
.elaboracao-layout {
|
| 4694 |
+
grid-template-columns: 1fr;
|
| 4695 |
+
gap: 10px;
|
| 4696 |
+
}
|
| 4697 |
+
|
| 4698 |
+
.elaboracao-side-nav {
|
| 4699 |
+
top: 68px;
|
| 4700 |
+
width: 100%;
|
| 4701 |
+
overflow-x: auto;
|
| 4702 |
+
overflow-y: visible;
|
| 4703 |
+
padding-bottom: 4px;
|
| 4704 |
+
}
|
| 4705 |
+
|
| 4706 |
+
.elaboracao-side-nav-list {
|
| 4707 |
+
flex-direction: row;
|
| 4708 |
+
gap: 6px;
|
| 4709 |
+
}
|
| 4710 |
+
|
| 4711 |
+
.elaboracao-side-nav-label {
|
| 4712 |
+
display: none;
|
| 4713 |
+
}
|
| 4714 |
+
|
| 4715 |
+
.elaboracao-side-nav-item {
|
| 4716 |
+
width: 32px;
|
| 4717 |
+
height: 32px;
|
| 4718 |
+
flex: 0 0 auto;
|
| 4719 |
+
}
|
| 4720 |
+
|
| 4721 |
.tabs {
|
| 4722 |
grid-template-columns: 1fr;
|
| 4723 |
}
|