feat: add min hours-to-expiry filter on ops map (8-48h step 2)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-02 10:42:55 +08:00
parent f798c11cf9
commit 3e492eb46c
7 changed files with 143 additions and 10 deletions
+2
View File
@@ -251,12 +251,14 @@ export async function fetchOpsMap(params: {
date?: string;
side?: string;
bucket_minutes?: number;
min_hours?: number;
}): Promise<OpsMap> {
const q = new URLSearchParams();
q.set("range", params.range);
if (params.date) q.set("date", params.date);
if (params.side) q.set("side", params.side);
if (params.bucket_minutes) q.set("bucket_minutes", String(params.bucket_minutes));
if (params.min_hours != null) q.set("min_hours", String(params.min_hours));
const r = await apiFetch(`/api/stats/ops-map?${q}`);
if (!r.ok) throw new Error(r.status === 401 ? "unauthorized" : "ops-map failed");
return r.json();
+19 -2
View File
@@ -15,6 +15,8 @@ import { leverageSummary, moveSummary } from "../lib/opsSummary";
type RangeKey = "day" | "week" | "month";
type SideKey = "both" | "C" | "P";
const MIN_HOURS_OPTIONS = Array.from({ length: (48 - 8) / 2 + 1 }, (_, i) => 8 + i * 2);
function todayYmd() {
const d = new Date();
const y = d.getFullYear();
@@ -29,6 +31,7 @@ export default function OpsMapPage() {
const [range, setRange] = useState<RangeKey>("day");
const [side, setSide] = useState<SideKey>("both");
const [date, setDate] = useState(todayYmd());
const [minHours, setMinHours] = useState(12);
const [moveMode, setMoveMode] = useState<"abs" | "signed">("abs");
const [data, setData] = useState<OpsMap | null>(null);
const [err, setErr] = useState<string | null>(null);
@@ -52,7 +55,7 @@ export default function OpsMapPage() {
if (!ready || needLogin) return;
let alive = true;
setLoading(true);
fetchOpsMap({ range, date, side, bucket_minutes: 60 })
fetchOpsMap({ range, date, side, bucket_minutes: 60, min_hours: minHours })
.then((d) => {
if (!alive) return;
setData(d);
@@ -70,7 +73,7 @@ export default function OpsMapPage() {
return () => {
alive = false;
};
}, [range, side, date, ready, needLogin]);
}, [range, side, date, minHours, ready, needLogin]);
if (!ready) {
return (
@@ -122,6 +125,20 @@ export default function OpsMapPage() {
<input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
</label>
<label className="date-field">
<select
value={minHours}
onChange={(e) => setMinHours(Number(e.target.value))}
aria-label="距离到期最小小时数"
>
{MIN_HOURS_OPTIONS.map((h) => (
<option key={h} value={h}>
{h}h
</option>
))}
</select>
</label>
</div>
{err && <p style={{ color: "#e85d5d" }}>{err}</p>}
+2 -1
View File
@@ -159,7 +159,8 @@ select.field-input { cursor: pointer; }
color: var(--muted);
font-size: 0.85rem;
}
.date-field input {
.date-field input,
.date-field select {
background: var(--panel);
border: 1px solid #243041;
color: var(--text);