mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-30 02:14:43 +08:00
feat(usage): add live end time option for custom date range (#4438)
* add glm-5.2 pricing * feat(usage): add live end time option for custom date range Add a "End time follows current time" checkbox in the custom date range picker. When enabled, the end time becomes read-only and automatically tracks the current moment, so usage data always reflects up-to-the-second consumption from the chosen start time. This is especially useful under the Coding Plan 5-hour quota window — users can set the start time to when their 5h window began and keep the end time live to monitor real-time token consumption. * style(usage): fix prettier formatting for UsageDashboard * fix(usage): include liveEndTime in React Query cache keys Without liveEndTime in the query key, a live custom range and a fixed custom range with the same stored endpoints share the same cache entry. After the live range refreshes (endDate = now), switching to the fixed range shows stale data fetched through "now" instead of the original end time.
This commit is contained in:
@@ -108,9 +108,18 @@ export function UsageDashboard() {
|
|||||||
return getUsageRangePresetLabel(range.preset, t);
|
return getUsageRangePresetLabel(range.preset, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${new Date(resolvedRange.startDate * 1000).toLocaleString(locale)} - ${new Date(
|
const startStr = new Date(resolvedRange.startDate * 1000).toLocaleString(
|
||||||
resolvedRange.endDate * 1000,
|
locale,
|
||||||
).toLocaleString(locale)}`;
|
);
|
||||||
|
|
||||||
|
if (range.liveEndTime) {
|
||||||
|
return `${startStr} → ${t("usage.liveEndTimeNow", "现在")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endStr = new Date(resolvedRange.endDate * 1000).toLocaleString(
|
||||||
|
locale,
|
||||||
|
);
|
||||||
|
return `${startStr} - ${endStr}`;
|
||||||
}, [locale, range, resolvedRange.endDate, resolvedRange.startDate, t]);
|
}, [locale, range, resolvedRange.endDate, resolvedRange.startDate, t]);
|
||||||
|
|
||||||
// 顶栏下拉的选项池:Provider 列表只跟应用/时间范围走(不受自身选中值影响),
|
// 顶栏下拉的选项池:Provider 列表只跟应用/时间范围走(不受自身选中值影响),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ChevronRight,
|
ChevronRight,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
@@ -117,6 +118,9 @@ export function UsageDateRangePicker({
|
|||||||
);
|
);
|
||||||
const [draftStart, setDraftStart] = useState(resolvedRange.startDate);
|
const [draftStart, setDraftStart] = useState(resolvedRange.startDate);
|
||||||
const [draftEnd, setDraftEnd] = useState(resolvedRange.endDate);
|
const [draftEnd, setDraftEnd] = useState(resolvedRange.endDate);
|
||||||
|
const [draftLiveEnd, setDraftLiveEnd] = useState(
|
||||||
|
selection.preset === "custom" ? (selection.liveEndTime ?? false) : false,
|
||||||
|
);
|
||||||
const [displayMonth, setDisplayMonth] = useState(
|
const [displayMonth, setDisplayMonth] = useState(
|
||||||
() =>
|
() =>
|
||||||
new Date(
|
new Date(
|
||||||
@@ -136,6 +140,9 @@ export function UsageDateRangePicker({
|
|||||||
const r = resolveUsageRange(selection);
|
const r = resolveUsageRange(selection);
|
||||||
setDraftStart(r.startDate);
|
setDraftStart(r.startDate);
|
||||||
setDraftEnd(r.endDate);
|
setDraftEnd(r.endDate);
|
||||||
|
setDraftLiveEnd(
|
||||||
|
selection.preset === "custom" ? (selection.liveEndTime ?? false) : false,
|
||||||
|
);
|
||||||
setDisplayMonth(
|
setDisplayMonth(
|
||||||
new Date(
|
new Date(
|
||||||
fromTs(r.startDate).getFullYear(),
|
fromTs(r.startDate).getFullYear(),
|
||||||
@@ -147,6 +154,15 @@ export function UsageDateRangePicker({
|
|||||||
setError(null);
|
setError(null);
|
||||||
}, [open, selection]);
|
}, [open, selection]);
|
||||||
|
|
||||||
|
// Keep draftEnd ticking when live mode is active and popover is open
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open || !draftLiveEnd) return;
|
||||||
|
const tick = () => setDraftEnd(Math.floor(Date.now() / 1000));
|
||||||
|
tick();
|
||||||
|
const id = setInterval(tick, 1000);
|
||||||
|
return () => clearInterval(id);
|
||||||
|
}, [open, draftLiveEnd]);
|
||||||
|
|
||||||
const calendarDays = useMemo(
|
const calendarDays = useMemo(
|
||||||
() => getCalendarDays(displayMonth),
|
() => getCalendarDays(displayMonth),
|
||||||
[displayMonth],
|
[displayMonth],
|
||||||
@@ -169,6 +185,14 @@ export function UsageDateRangePicker({
|
|||||||
/* Pick a date from the calendar */
|
/* Pick a date from the calendar */
|
||||||
const handleDatePick = (day: Date) => {
|
const handleDatePick = (day: Date) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
// When live end time is active, calendar only controls start date
|
||||||
|
if (draftLiveEnd) {
|
||||||
|
const nextTs = setDateKeepTime(draftStart, day);
|
||||||
|
setDraftStart(nextTs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const nextTs = setDateKeepTime(
|
const nextTs = setDateKeepTime(
|
||||||
activeField === "start" ? draftStart : draftEnd,
|
activeField === "start" ? draftStart : draftEnd,
|
||||||
day,
|
day,
|
||||||
@@ -211,6 +235,7 @@ export function UsageDateRangePicker({
|
|||||||
preset: "custom",
|
preset: "custom",
|
||||||
customStartDate: draftStart,
|
customStartDate: draftStart,
|
||||||
customEndDate: draftEnd,
|
customEndDate: draftEnd,
|
||||||
|
liveEndTime: draftLiveEnd,
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
@@ -222,6 +247,7 @@ export function UsageDateRangePicker({
|
|||||||
/* ── Field card (start / end) ── */
|
/* ── Field card (start / end) ── */
|
||||||
const renderField = (field: DraftField) => {
|
const renderField = (field: DraftField) => {
|
||||||
const isActive = activeField === field;
|
const isActive = activeField === field;
|
||||||
|
const isEndLive = field === "end" && draftLiveEnd;
|
||||||
const ts = field === "start" ? draftStart : draftEnd;
|
const ts = field === "start" ? draftStart : draftEnd;
|
||||||
const setTs = field === "start" ? setDraftStart : setDraftEnd;
|
const setTs = field === "start" ? setDraftStart : setDraftEnd;
|
||||||
const label =
|
const label =
|
||||||
@@ -232,12 +258,16 @@ export function UsageDateRangePicker({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-lg border px-3 py-2 cursor-pointer transition-all",
|
"rounded-lg border px-3 py-2 transition-all",
|
||||||
isActive
|
isEndLive
|
||||||
? "border-primary ring-1 ring-primary/30 bg-primary/5"
|
? "border-border/30 bg-muted/30 cursor-not-allowed opacity-50"
|
||||||
: "border-border/50 hover:border-border",
|
: isActive
|
||||||
|
? "border-primary ring-1 ring-primary/30 bg-primary/5 cursor-pointer"
|
||||||
|
: "border-border/50 hover:border-border cursor-pointer",
|
||||||
)}
|
)}
|
||||||
onClick={() => setActiveField(field)}
|
onClick={() => {
|
||||||
|
if (!isEndLive) setActiveField(field);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="mb-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
<div className="mb-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
||||||
{label}
|
{label}
|
||||||
@@ -245,27 +275,41 @@ export function UsageDateRangePicker({
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
className="h-7 flex-1 border-0 bg-transparent p-0 text-sm shadow-none focus-visible:ring-0"
|
className={cn(
|
||||||
|
"h-7 flex-1 border-0 bg-transparent p-0 text-sm shadow-none focus-visible:ring-0",
|
||||||
|
isEndLive && "pointer-events-none",
|
||||||
|
)}
|
||||||
value={fmtDate(ts)}
|
value={fmtDate(ts)}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
if (isEndLive) return;
|
||||||
const next = parseDateInput(ts, e.target.value);
|
const next = parseDateInput(ts, e.target.value);
|
||||||
setTs(next);
|
setTs(next);
|
||||||
const d = fromTs(next);
|
const d = fromTs(next);
|
||||||
setDisplayMonth(new Date(d.getFullYear(), d.getMonth(), 1));
|
setDisplayMonth(new Date(d.getFullYear(), d.getMonth(), 1));
|
||||||
setError(null);
|
setError(null);
|
||||||
}}
|
}}
|
||||||
onFocus={() => setActiveField(field)}
|
onFocus={() => {
|
||||||
|
if (!isEndLive) setActiveField(field);
|
||||||
|
}}
|
||||||
|
readOnly={isEndLive}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
type="time"
|
type="time"
|
||||||
step={60}
|
step={60}
|
||||||
className="h-7 w-[90px] flex-none border-0 bg-transparent p-0 text-sm shadow-none focus-visible:ring-0"
|
className={cn(
|
||||||
|
"h-7 w-[90px] flex-none border-0 bg-transparent p-0 text-sm shadow-none focus-visible:ring-0",
|
||||||
|
isEndLive && "pointer-events-none",
|
||||||
|
)}
|
||||||
value={fmtTime(ts)}
|
value={fmtTime(ts)}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
if (isEndLive) return;
|
||||||
setTs(parseTimeInput(ts, e.target.value));
|
setTs(parseTimeInput(ts, e.target.value));
|
||||||
setError(null);
|
setError(null);
|
||||||
}}
|
}}
|
||||||
onFocus={() => setActiveField(field)}
|
onFocus={() => {
|
||||||
|
if (!isEndLive) setActiveField(field);
|
||||||
|
}}
|
||||||
|
readOnly={isEndLive}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -318,6 +362,23 @@ export function UsageDateRangePicker({
|
|||||||
{renderField("start")}
|
{renderField("start")}
|
||||||
{renderField("end")}
|
{renderField("end")}
|
||||||
|
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer select-none">
|
||||||
|
<Checkbox
|
||||||
|
checked={draftLiveEnd}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
const live = checked === true;
|
||||||
|
setDraftLiveEnd(live);
|
||||||
|
if (live) {
|
||||||
|
setDraftEnd(Math.floor(Date.now() / 1000));
|
||||||
|
setActiveField("start");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{t("usage.liveEndTime", "结束时间跟随当前时刻")}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
{error && <p className="text-xs text-destructive">{error}</p>}
|
{error && <p className="text-xs text-destructive">{error}</p>}
|
||||||
|
|
||||||
<div className="flex gap-2 pt-1">
|
<div className="flex gap-2 pt-1">
|
||||||
|
|||||||
@@ -1473,6 +1473,8 @@
|
|||||||
"customRangeHint": "Supports both date and time",
|
"customRangeHint": "Supports both date and time",
|
||||||
"startTime": "Start Time",
|
"startTime": "Start Time",
|
||||||
"endTime": "End Time",
|
"endTime": "End Time",
|
||||||
|
"liveEndTime": "End time follows current time",
|
||||||
|
"liveEndTimeNow": "Now",
|
||||||
"input": "Input",
|
"input": "Input",
|
||||||
"output": "Output",
|
"output": "Output",
|
||||||
"cacheWrite": "Creation",
|
"cacheWrite": "Creation",
|
||||||
|
|||||||
@@ -1473,6 +1473,8 @@
|
|||||||
"customRangeHint": "日付と時刻の両方に対応",
|
"customRangeHint": "日付と時刻の両方に対応",
|
||||||
"startTime": "開始時刻",
|
"startTime": "開始時刻",
|
||||||
"endTime": "終了時刻",
|
"endTime": "終了時刻",
|
||||||
|
"liveEndTime": "終了時刻を現在時刻に追従",
|
||||||
|
"liveEndTimeNow": "現在",
|
||||||
"input": "Input",
|
"input": "Input",
|
||||||
"output": "Output",
|
"output": "Output",
|
||||||
"cacheWrite": "作成",
|
"cacheWrite": "作成",
|
||||||
|
|||||||
@@ -1445,6 +1445,8 @@
|
|||||||
"customRangeHint": "支援日期與時間",
|
"customRangeHint": "支援日期與時間",
|
||||||
"startTime": "開始時間",
|
"startTime": "開始時間",
|
||||||
"endTime": "結束時間",
|
"endTime": "結束時間",
|
||||||
|
"liveEndTime": "結束時間跟隨當前時刻",
|
||||||
|
"liveEndTimeNow": "現在",
|
||||||
"input": "Input",
|
"input": "Input",
|
||||||
"output": "Output",
|
"output": "Output",
|
||||||
"cacheWrite": "建立",
|
"cacheWrite": "建立",
|
||||||
|
|||||||
@@ -1473,6 +1473,8 @@
|
|||||||
"customRangeHint": "支持日期与时间",
|
"customRangeHint": "支持日期与时间",
|
||||||
"startTime": "开始时间",
|
"startTime": "开始时间",
|
||||||
"endTime": "结束时间",
|
"endTime": "结束时间",
|
||||||
|
"liveEndTime": "结束时间跟随当前时刻",
|
||||||
|
"liveEndTimeNow": "现在",
|
||||||
"input": "Input",
|
"input": "Input",
|
||||||
"output": "Output",
|
"output": "Output",
|
||||||
"cacheWrite": "创建",
|
"cacheWrite": "创建",
|
||||||
|
|||||||
+19
-1
@@ -26,6 +26,7 @@ type RequestLogsKey = {
|
|||||||
preset: UsageRangeSelection["preset"];
|
preset: UsageRangeSelection["preset"];
|
||||||
customStartDate?: number;
|
customStartDate?: number;
|
||||||
customEndDate?: number;
|
customEndDate?: number;
|
||||||
|
liveEndTime?: boolean;
|
||||||
appType?: string;
|
appType?: string;
|
||||||
providerName?: string;
|
providerName?: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
@@ -40,6 +41,7 @@ export const usageKeys = {
|
|||||||
customStartDate: number | undefined,
|
customStartDate: number | undefined,
|
||||||
customEndDate: number | undefined,
|
customEndDate: number | undefined,
|
||||||
filters?: UsageScopeFilters,
|
filters?: UsageScopeFilters,
|
||||||
|
liveEndTime?: boolean,
|
||||||
) =>
|
) =>
|
||||||
[
|
[
|
||||||
...usageKeys.all,
|
...usageKeys.all,
|
||||||
@@ -47,6 +49,7 @@ export const usageKeys = {
|
|||||||
preset,
|
preset,
|
||||||
customStartDate ?? 0,
|
customStartDate ?? 0,
|
||||||
customEndDate ?? 0,
|
customEndDate ?? 0,
|
||||||
|
liveEndTime ?? false,
|
||||||
filters?.appType ?? null,
|
filters?.appType ?? null,
|
||||||
filters?.providerName ?? null,
|
filters?.providerName ?? null,
|
||||||
filters?.model ?? null,
|
filters?.model ?? null,
|
||||||
@@ -55,7 +58,8 @@ export const usageKeys = {
|
|||||||
preset: UsageRangeSelection["preset"],
|
preset: UsageRangeSelection["preset"],
|
||||||
customStartDate: number | undefined,
|
customStartDate: number | undefined,
|
||||||
customEndDate: number | undefined,
|
customEndDate: number | undefined,
|
||||||
filters?: UsageScopeFilters,
|
filters?: Pick<UsageScopeFilters, "providerName" | "model">,
|
||||||
|
liveEndTime?: boolean,
|
||||||
) =>
|
) =>
|
||||||
[
|
[
|
||||||
...usageKeys.all,
|
...usageKeys.all,
|
||||||
@@ -63,6 +67,7 @@ export const usageKeys = {
|
|||||||
preset,
|
preset,
|
||||||
customStartDate ?? 0,
|
customStartDate ?? 0,
|
||||||
customEndDate ?? 0,
|
customEndDate ?? 0,
|
||||||
|
liveEndTime ?? false,
|
||||||
filters?.providerName ?? null,
|
filters?.providerName ?? null,
|
||||||
filters?.model ?? null,
|
filters?.model ?? null,
|
||||||
] as const,
|
] as const,
|
||||||
@@ -71,6 +76,7 @@ export const usageKeys = {
|
|||||||
customStartDate: number | undefined,
|
customStartDate: number | undefined,
|
||||||
customEndDate: number | undefined,
|
customEndDate: number | undefined,
|
||||||
filters?: UsageScopeFilters,
|
filters?: UsageScopeFilters,
|
||||||
|
liveEndTime?: boolean,
|
||||||
) =>
|
) =>
|
||||||
[
|
[
|
||||||
...usageKeys.all,
|
...usageKeys.all,
|
||||||
@@ -78,6 +84,7 @@ export const usageKeys = {
|
|||||||
preset,
|
preset,
|
||||||
customStartDate ?? 0,
|
customStartDate ?? 0,
|
||||||
customEndDate ?? 0,
|
customEndDate ?? 0,
|
||||||
|
liveEndTime ?? false,
|
||||||
filters?.appType ?? null,
|
filters?.appType ?? null,
|
||||||
filters?.providerName ?? null,
|
filters?.providerName ?? null,
|
||||||
filters?.model ?? null,
|
filters?.model ?? null,
|
||||||
@@ -87,6 +94,7 @@ export const usageKeys = {
|
|||||||
customStartDate: number | undefined,
|
customStartDate: number | undefined,
|
||||||
customEndDate: number | undefined,
|
customEndDate: number | undefined,
|
||||||
filters?: UsageScopeFilters,
|
filters?: UsageScopeFilters,
|
||||||
|
liveEndTime?: boolean,
|
||||||
) =>
|
) =>
|
||||||
[
|
[
|
||||||
...usageKeys.all,
|
...usageKeys.all,
|
||||||
@@ -94,6 +102,7 @@ export const usageKeys = {
|
|||||||
preset,
|
preset,
|
||||||
customStartDate ?? 0,
|
customStartDate ?? 0,
|
||||||
customEndDate ?? 0,
|
customEndDate ?? 0,
|
||||||
|
liveEndTime ?? false,
|
||||||
filters?.appType ?? null,
|
filters?.appType ?? null,
|
||||||
filters?.providerName ?? null,
|
filters?.providerName ?? null,
|
||||||
filters?.model ?? null,
|
filters?.model ?? null,
|
||||||
@@ -103,6 +112,7 @@ export const usageKeys = {
|
|||||||
customStartDate: number | undefined,
|
customStartDate: number | undefined,
|
||||||
customEndDate: number | undefined,
|
customEndDate: number | undefined,
|
||||||
filters?: UsageScopeFilters,
|
filters?: UsageScopeFilters,
|
||||||
|
liveEndTime?: boolean,
|
||||||
) =>
|
) =>
|
||||||
[
|
[
|
||||||
...usageKeys.all,
|
...usageKeys.all,
|
||||||
@@ -110,6 +120,7 @@ export const usageKeys = {
|
|||||||
preset,
|
preset,
|
||||||
customStartDate ?? 0,
|
customStartDate ?? 0,
|
||||||
customEndDate ?? 0,
|
customEndDate ?? 0,
|
||||||
|
liveEndTime ?? false,
|
||||||
filters?.appType ?? null,
|
filters?.appType ?? null,
|
||||||
filters?.providerName ?? null,
|
filters?.providerName ?? null,
|
||||||
filters?.model ?? null,
|
filters?.model ?? null,
|
||||||
@@ -121,6 +132,7 @@ export const usageKeys = {
|
|||||||
key.preset,
|
key.preset,
|
||||||
key.customStartDate ?? 0,
|
key.customStartDate ?? 0,
|
||||||
key.customEndDate ?? 0,
|
key.customEndDate ?? 0,
|
||||||
|
key.liveEndTime ?? false,
|
||||||
key.appType ?? "",
|
key.appType ?? "",
|
||||||
key.providerName ?? "",
|
key.providerName ?? "",
|
||||||
key.model ?? "",
|
key.model ?? "",
|
||||||
@@ -159,6 +171,7 @@ export function useUsageSummary(
|
|||||||
range.customStartDate,
|
range.customStartDate,
|
||||||
range.customEndDate,
|
range.customEndDate,
|
||||||
effective,
|
effective,
|
||||||
|
range.liveEndTime,
|
||||||
),
|
),
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
const { startDate, endDate } = resolveUsageRange(range);
|
const { startDate, endDate } = resolveUsageRange(range);
|
||||||
@@ -186,6 +199,7 @@ export function useUsageSummaryByApp(
|
|||||||
range.customStartDate,
|
range.customStartDate,
|
||||||
range.customEndDate,
|
range.customEndDate,
|
||||||
filters,
|
filters,
|
||||||
|
range.liveEndTime,
|
||||||
),
|
),
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
const { startDate, endDate } = resolveUsageRange(range);
|
const { startDate, endDate } = resolveUsageRange(range);
|
||||||
@@ -213,6 +227,7 @@ export function useUsageTrends(
|
|||||||
range.customStartDate,
|
range.customStartDate,
|
||||||
range.customEndDate,
|
range.customEndDate,
|
||||||
effective,
|
effective,
|
||||||
|
range.liveEndTime,
|
||||||
),
|
),
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
const { startDate, endDate } = resolveUsageRange(range);
|
const { startDate, endDate } = resolveUsageRange(range);
|
||||||
@@ -241,6 +256,7 @@ export function useProviderStats(
|
|||||||
range.customStartDate,
|
range.customStartDate,
|
||||||
range.customEndDate,
|
range.customEndDate,
|
||||||
effective,
|
effective,
|
||||||
|
range.liveEndTime,
|
||||||
),
|
),
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
const { startDate, endDate } = resolveUsageRange(range);
|
const { startDate, endDate } = resolveUsageRange(range);
|
||||||
@@ -269,6 +285,7 @@ export function useModelStats(
|
|||||||
range.customStartDate,
|
range.customStartDate,
|
||||||
range.customEndDate,
|
range.customEndDate,
|
||||||
effective,
|
effective,
|
||||||
|
range.liveEndTime,
|
||||||
),
|
),
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
const { startDate, endDate } = resolveUsageRange(range);
|
const { startDate, endDate } = resolveUsageRange(range);
|
||||||
@@ -296,6 +313,7 @@ export function useRequestLogs({
|
|||||||
preset: range.preset,
|
preset: range.preset,
|
||||||
customStartDate: range.customStartDate,
|
customStartDate: range.customStartDate,
|
||||||
customEndDate: range.customEndDate,
|
customEndDate: range.customEndDate,
|
||||||
|
liveEndTime: range.liveEndTime,
|
||||||
appType: filters.appType,
|
appType: filters.appType,
|
||||||
providerName: filters.providerName,
|
providerName: filters.providerName,
|
||||||
model: filters.model,
|
model: filters.model,
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ export function resolveUsageRange(
|
|||||||
};
|
};
|
||||||
case "custom": {
|
case "custom": {
|
||||||
const startDate = selection.customStartDate ?? endDate - DAY_SECONDS;
|
const startDate = selection.customStartDate ?? endDate - DAY_SECONDS;
|
||||||
const customEndDate = selection.customEndDate ?? endDate;
|
const customEndDate = selection.liveEndTime
|
||||||
|
? endDate
|
||||||
|
: (selection.customEndDate ?? endDate);
|
||||||
return {
|
return {
|
||||||
startDate,
|
startDate,
|
||||||
endDate: customEndDate,
|
endDate: customEndDate,
|
||||||
|
|||||||
@@ -152,6 +152,9 @@ export interface UsageRangeSelection {
|
|||||||
preset: UsageRangePreset;
|
preset: UsageRangePreset;
|
||||||
customStartDate?: number;
|
customStartDate?: number;
|
||||||
customEndDate?: number;
|
customEndDate?: number;
|
||||||
|
/** When true (custom mode only), endDate resolves to "now" instead of the
|
||||||
|
* fixed customEndDate snapshot, and the end-time field becomes read-only. */
|
||||||
|
liveEndTime?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user