Files
wh40k-points-comparator/react-app/src/App.jsx
Kaysser Kayyali 598d3925a5 fix: restore default faction + remove dead data-testid attributes
- 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)
2026-06-23 17:41:06 -04:00

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>
)
}