- Restore default faction to 'adepta-sororitas' on first load (matches pre-refactor behavior; was a silent behavior change) - Add regression test asserting default-load shows only Adepta Sororitas units and hides the Faction column - Remove unused data-testid="app-root" from App.jsx and data-testid="mover-row" from MoversPanel.jsx (no test queried either)
109 lines
3.9 KiB
JavaScript
109 lines
3.9 KiB
JavaScript
import React, { useState, useMemo, useCallback, useEffect } from 'react'
|
|
import { Box, useMediaQuery, useTheme } from '@mui/material'
|
|
import { FilterBar } from './components/FilterBar.jsx'
|
|
import { MoversSection } from './components/MoversSection.jsx'
|
|
import { UnitTable } from './components/UnitTable.jsx'
|
|
import { GraphModal } from './components/GraphModal.jsx'
|
|
import { computeMovers } from './utils/movers.js'
|
|
import { buildColumns } from './utils/columns.jsx'
|
|
import { readFiltersFromUrl, writeFiltersToUrl } from './utils/url.js'
|
|
|
|
export default function App() {
|
|
const [data, setData] = useState(null)
|
|
const initial = useMemo(() => readFiltersFromUrl(), [])
|
|
const [query, setQuery] = useState(initial.q)
|
|
const [faction, setFaction] = useState(initial.faction || 'adepta-sororitas')
|
|
const [dir, setDir] = useState(initial.dir)
|
|
const [sizeChoice, setSizeChoice] = useState({})
|
|
const [modalRow, setModalRow] = useState(null)
|
|
|
|
const theme = useTheme()
|
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
|
|
|
|
useEffect(() => {
|
|
fetch('./data.json').then(r => r.json()).then(setData).catch(console.error)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
writeFiltersToUrl({ q: query, faction, dir })
|
|
}, [query, faction, dir])
|
|
|
|
const selectSize = useCallback((row, sizeLabel) => {
|
|
const key = `${row.faction}|${row.name}`
|
|
setSizeChoice(prev => ({ ...prev, [key]: sizeLabel }))
|
|
}, [])
|
|
|
|
const augmented = useMemo(() => {
|
|
if (!data) return []
|
|
return data.units.map(u => {
|
|
const key = `${u.faction}|${u.name}`
|
|
const chosen = sizeChoice[key]
|
|
const active = (chosen && u.sizes.find(s => s.size === chosen)) || u.sizes.find(s => s.size === u.default_size) || u.sizes[0]
|
|
return {
|
|
...u,
|
|
size: active.size,
|
|
original: active.original,
|
|
new: active.new,
|
|
change_pct: active.change_pct,
|
|
change_pts: active.change_pts,
|
|
tier: active.tier,
|
|
}
|
|
})
|
|
}, [data, sizeChoice])
|
|
|
|
const filtered = useMemo(() => {
|
|
if (!augmented.length) return []
|
|
let view = augmented
|
|
const q = query.trim().toLowerCase()
|
|
if (q) {
|
|
view = view.filter(u =>
|
|
u.name.toLowerCase().includes(q) ||
|
|
u.faction_name.toLowerCase().includes(q) ||
|
|
u.size.toLowerCase().includes(q)
|
|
)
|
|
}
|
|
if (faction) view = view.filter(u => u.faction === faction)
|
|
if (dir === 'up') view = view.filter(u => u.change_pct !== null && u.change_pct > 0)
|
|
else if (dir === 'down') view = view.filter(u => u.change_pct !== null && u.change_pct < 0)
|
|
else if (dir === 'no-change') view = view.filter(u => u.change_pct === 0)
|
|
else if (dir === 'new-only') view = view.filter(u => u.original === null && u.new !== null)
|
|
else if (dir === 'old-only') view = view.filter(u => u.original !== null && u.new === null)
|
|
return view
|
|
}, [augmented, query, faction, dir])
|
|
|
|
const movers = useMemo(() => computeMovers(filtered), [filtered])
|
|
const showFactionCol = !faction
|
|
const showFactionInMovers = !faction
|
|
const columns = useMemo(
|
|
() => buildColumns({ isMobile, showFactionCol, onSelectSize: selectSize }),
|
|
[isMobile, showFactionCol, selectSize],
|
|
)
|
|
|
|
if (!data) return <Box sx={{ p: 4, color: 'text.secondary' }}>Loading…</Box>
|
|
|
|
return (
|
|
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default' }}>
|
|
<FilterBar
|
|
query={query} setQuery={setQuery}
|
|
faction={faction} setFaction={setFaction}
|
|
dir={dir} setDir={setDir}
|
|
factions={data.factions}
|
|
factionNames={data.faction_names}
|
|
isMobile={isMobile}
|
|
totalRows={data.stats.total_rows}
|
|
/>
|
|
<MoversSection
|
|
movers={movers}
|
|
onSelectUnit={setModalRow}
|
|
showFaction={showFactionInMovers}
|
|
/>
|
|
<UnitTable
|
|
rows={filtered}
|
|
columns={columns}
|
|
filteredCount={filtered.length}
|
|
onCellClick={setModalRow}
|
|
/>
|
|
<GraphModal row={modalRow} open={!!modalRow} onClose={() => setModalRow(null)} />
|
|
</Box>
|
|
)
|
|
} |