feat: add filter and sort controller

This commit is contained in:
2026-04-20 20:07:53 +08:00
parent 98dc078a15
commit 26bdff8daa
3 changed files with 201 additions and 0 deletions
@@ -0,0 +1,95 @@
import {
compareRateValues,
parseRateLowerBound
} from "../../shared/rate-normalizer";
import type {
MarketFilterState,
MarketRecord,
MarketSortState
} from "./types";
interface ApplyFilterAndSortOptions {
filters?: MarketFilterState;
sort?: MarketSortState;
}
export function applyFilterAndSort(
records: MarketRecord[],
options: ApplyFilterAndSortOptions = {}
): MarketRecord[] {
const filteredRecords = records.filter((record) =>
matchesFilters(record, options.filters)
);
if (!options.sort) {
return filteredRecords;
}
return [...filteredRecords].sort((leftRecord, rightRecord) =>
compareRecords(leftRecord, rightRecord, options.sort as MarketSortState)
);
}
function matchesFilters(
record: MarketRecord,
filters: MarketFilterState | undefined
): boolean {
if (!filters) {
return true;
}
return (
meetsThreshold(
record.rates?.singleVideoAfterSearchRate,
filters.singleVideoAfterSearchRateMin
) &&
meetsThreshold(
record.rates?.personalVideoAfterSearchRate,
filters.personalVideoAfterSearchRateMin
)
);
}
function meetsThreshold(
rateValue: string | undefined,
minValue: number | undefined
): boolean {
if (minValue == null) {
return true;
}
const lowerBound = parseRateLowerBound(rateValue ?? null);
return lowerBound != null && lowerBound >= minValue;
}
function compareRecords(
leftRecord: MarketRecord,
rightRecord: MarketRecord,
sort: MarketSortState
): number {
const leftValue = leftRecord.rates?.[sort.field];
const rightValue = rightRecord.rates?.[sort.field];
const leftLowerBound = parseRateLowerBound(leftValue ?? null);
const rightLowerBound = parseRateLowerBound(rightValue ?? null);
if (leftLowerBound == null && rightLowerBound == null) {
return 0;
}
if (leftLowerBound == null) {
return 1;
}
if (rightLowerBound == null) {
return -1;
}
if (leftLowerBound !== rightLowerBound) {
return sort.direction === "asc"
? leftLowerBound - rightLowerBound
: rightLowerBound - leftLowerBound;
}
const tieBreak = compareRateValues(leftValue, rightValue);
return sort.direction === "asc" ? tieBreak : -tieBreak;
}
+10
View File
@@ -18,6 +18,16 @@ export interface MarketRecord extends MarketRowSnapshot {
rates?: Required<AfterSearchRates>;
}
export interface MarketFilterState {
personalVideoAfterSearchRateMin?: number;
singleVideoAfterSearchRateMin?: number;
}
export interface MarketSortState {
direction: "asc" | "desc";
field: keyof Required<AfterSearchRates>;
}
export type MarketApiFailureReason =
| "bad-response"
| "missing-rate"