fix(dashboard): trades pagination + reproducible Docker build

- Add pagination controls to Trades tab (prev/next, offset support)
- Reset page on symbol change
- Use package-lock.json + npm ci for reproducible UI builds

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
21in7
2026-03-21 17:15:48 +09:00
parent 13c2b95c8e
commit e3623293f7
2 changed files with 39 additions and 6 deletions

View File

@@ -1,7 +1,7 @@
FROM node:20-alpine AS build FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
COPY package.json . COPY package.json package-lock.json .
RUN npm install RUN npm ci
COPY . . COPY . .
RUN npm run build RUN npm run build

View File

@@ -279,6 +279,7 @@ export default function App() {
const [botStatus, setBotStatus] = useState({}); const [botStatus, setBotStatus] = useState({});
const [trades, setTrades] = useState([]); const [trades, setTrades] = useState([]);
const [tradesTotal, setTradesTotal] = useState(0); const [tradesTotal, setTradesTotal] = useState(0);
const [tradesPage, setTradesPage] = useState(0);
const [daily, setDaily] = useState([]); const [daily, setDaily] = useState([]);
const [candles, setCandles] = useState([]); const [candles, setCandles] = useState([]);
@@ -291,7 +292,7 @@ export default function App() {
api("/symbols"), api("/symbols"),
api(`/stats${sym}`), api(`/stats${sym}`),
api(`/position${sym}`), api(`/position${sym}`),
api(`/trades${sym}${sym ? "&" : "?"}limit=50`), api(`/trades${sym}${sym ? "&" : "?"}limit=50&offset=${tradesPage * 50}`),
api(`/daily${sym}`), api(`/daily${sym}`),
api(`/candles?symbol=${symRequired}&limit=96`), api(`/candles?symbol=${symRequired}&limit=96`),
]); ]);
@@ -315,7 +316,7 @@ export default function App() {
} }
if (dRes?.daily) setDaily(dRes.daily); if (dRes?.daily) setDaily(dRes.daily);
if (cRes?.candles) setCandles(cRes.candles); if (cRes?.candles) setCandles(cRes.candles);
}, [selectedSymbol]); }, [selectedSymbol, tradesPage]);
useEffect(() => { useEffect(() => {
fetchAll(); fetchAll();
@@ -460,7 +461,7 @@ export default function App() {
padding: 4, width: "fit-content", padding: 4, width: "fit-content",
}}> }}>
<button <button
onClick={() => setSelectedSymbol(null)} onClick={() => { setSelectedSymbol(null); setTradesPage(0); }}
style={{ style={{
background: selectedSymbol === null ? "rgba(99,102,241,0.15)" : "transparent", background: selectedSymbol === null ? "rgba(99,102,241,0.15)" : "transparent",
border: "none", border: "none",
@@ -472,7 +473,7 @@ export default function App() {
{symbols.map((sym) => ( {symbols.map((sym) => (
<button <button
key={sym} key={sym}
onClick={() => setSelectedSymbol(sym)} onClick={() => { setSelectedSymbol(sym); setTradesPage(0); }}
style={{ style={{
background: selectedSymbol === sym ? "rgba(99,102,241,0.15)" : "transparent", background: selectedSymbol === sym ? "rgba(99,102,241,0.15)" : "transparent",
border: "none", border: "none",
@@ -621,6 +622,38 @@ export default function App() {
onToggle={() => setExpanded(expanded === t.id ? null : t.id)} onToggle={() => setExpanded(expanded === t.id ? null : t.id)}
/> />
))} ))}
{tradesTotal > 50 && (
<div style={{
display: "flex", justifyContent: "center", alignItems: "center",
gap: 12, marginTop: 14,
}}>
<button
disabled={tradesPage === 0}
onClick={() => setTradesPage((p) => Math.max(0, p - 1))}
style={{
fontSize: 11, fontFamily: S.mono, padding: "6px 14px",
background: tradesPage === 0 ? "transparent" : "rgba(99,102,241,0.1)",
color: tradesPage === 0 ? S.text4 : S.indigo,
border: `1px solid ${tradesPage === 0 ? S.border : "rgba(99,102,241,0.2)"}`,
borderRadius: 8, cursor: tradesPage === 0 ? "default" : "pointer",
}}
> 이전</button>
<span style={{ fontSize: 11, color: S.text3, fontFamily: S.mono }}>
{tradesPage * 50 + 1}{Math.min((tradesPage + 1) * 50, tradesTotal)} / {tradesTotal}
</span>
<button
disabled={(tradesPage + 1) * 50 >= tradesTotal}
onClick={() => setTradesPage((p) => p + 1)}
style={{
fontSize: 11, fontFamily: S.mono, padding: "6px 14px",
background: (tradesPage + 1) * 50 >= tradesTotal ? "transparent" : "rgba(99,102,241,0.1)",
color: (tradesPage + 1) * 50 >= tradesTotal ? S.text4 : S.indigo,
border: `1px solid ${(tradesPage + 1) * 50 >= tradesTotal ? S.border : "rgba(99,102,241,0.2)"}`,
borderRadius: 8, cursor: (tradesPage + 1) * 50 >= tradesTotal ? "default" : "pointer",
}}
>다음 </button>
</div>
)}
</div> </div>
)} )}