merge Usage page into Costs as two tabs ('Spend' and 'Providers'),
extract shared date-range logic to useDateRange() hook, delete /usage
route and sidebar entry, fix quota-windows bugs from prior review
96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
import { useMemo, useState } from "react";
|
|
|
|
export type DatePreset = "mtd" | "7d" | "30d" | "ytd" | "all" | "custom";
|
|
|
|
export const PRESET_LABELS: Record<DatePreset, string> = {
|
|
mtd: "Month to Date",
|
|
"7d": "Last 7 Days",
|
|
"30d": "Last 30 Days",
|
|
ytd: "Year to Date",
|
|
all: "All Time",
|
|
custom: "Custom",
|
|
};
|
|
|
|
export const PRESET_KEYS: DatePreset[] = ["mtd", "7d", "30d", "ytd", "all", "custom"];
|
|
|
|
function computeRange(preset: DatePreset): { from: string; to: string } {
|
|
const now = new Date();
|
|
const to = now.toISOString();
|
|
switch (preset) {
|
|
case "mtd": {
|
|
const d = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
return { from: d.toISOString(), to };
|
|
}
|
|
case "7d": {
|
|
const d = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
return { from: d.toISOString(), to };
|
|
}
|
|
case "30d": {
|
|
const d = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
return { from: d.toISOString(), to };
|
|
}
|
|
case "ytd": {
|
|
const d = new Date(now.getFullYear(), 0, 1);
|
|
return { from: d.toISOString(), to };
|
|
}
|
|
case "all":
|
|
case "custom":
|
|
return { from: "", to: "" };
|
|
}
|
|
}
|
|
|
|
export interface UseDateRangeResult {
|
|
preset: DatePreset;
|
|
setPreset: (p: DatePreset) => void;
|
|
customFrom: string;
|
|
setCustomFrom: (v: string) => void;
|
|
customTo: string;
|
|
setCustomTo: (v: string) => void;
|
|
/** resolved iso strings ready to pass to api calls; empty string means unbounded */
|
|
from: string;
|
|
to: string;
|
|
/** false when preset=custom but both dates are not yet selected */
|
|
customReady: boolean;
|
|
}
|
|
|
|
export function useDateRange(): UseDateRangeResult {
|
|
const [preset, setPreset] = useState<DatePreset>("mtd");
|
|
const [customFrom, setCustomFrom] = useState("");
|
|
const [customTo, setCustomTo] = useState("");
|
|
|
|
const { from, to } = useMemo(() => {
|
|
if (preset === "custom") {
|
|
// treat custom date strings as local-date boundaries so the full day is included
|
|
// regardless of the user's timezone. "from" starts at local midnight, "to" at 23:59:59.999.
|
|
const fromDate = customFrom ? new Date(customFrom + "T00:00:00") : null;
|
|
const toDate = customTo ? new Date(customTo + "T23:59:59.999") : null;
|
|
return {
|
|
from: fromDate ? fromDate.toISOString() : "",
|
|
to: toDate ? toDate.toISOString() : "",
|
|
};
|
|
}
|
|
const range = computeRange(preset);
|
|
// floor `to` to the nearest minute so the query key is stable across 30s refetch ticks
|
|
// (prevents a new cache entry being created on every poll cycle)
|
|
if (range.to) {
|
|
const d = new Date(range.to);
|
|
d.setSeconds(0, 0);
|
|
range.to = d.toISOString();
|
|
}
|
|
return range;
|
|
}, [preset, customFrom, customTo]);
|
|
|
|
const customReady = preset !== "custom" || (!!customFrom && !!customTo);
|
|
|
|
return {
|
|
preset,
|
|
setPreset,
|
|
customFrom,
|
|
setCustomFrom,
|
|
customTo,
|
|
setCustomTo,
|
|
from,
|
|
to,
|
|
customReady,
|
|
};
|
|
}
|