fix: restore production build dependencies
This commit is contained in:
111
platform/web/lib/match-order.ts
Normal file
111
platform/web/lib/match-order.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
export type MatchStatusKind = 'live' | 'upcoming' | 'finished' | 'postponed' | 'unknown';
|
||||
|
||||
type MatchStatusInput = {
|
||||
kickoff_utc?: string | null;
|
||||
match_time_utc?: string | null;
|
||||
match_time?: string | null;
|
||||
status?: string | null;
|
||||
home_score?: number | null;
|
||||
away_score?: number | null;
|
||||
};
|
||||
|
||||
const LIVE_WINDOW_MS = 150 * 60 * 1000;
|
||||
const RESULT_BACKFILL_GRACE_MS = 240 * 60 * 1000;
|
||||
|
||||
function kickoffTimestamp(match: MatchStatusInput): number | null {
|
||||
const raw = match.kickoff_utc || match.match_time_utc || match.match_time;
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const timestamp = Date.parse(raw);
|
||||
return Number.isNaN(timestamp) ? null : timestamp;
|
||||
}
|
||||
|
||||
function normalizedStatus(match: MatchStatusInput): string {
|
||||
return (match.status || '').trim().toLowerCase().replace(/_/g, '-');
|
||||
}
|
||||
|
||||
function hasScore(match: MatchStatusInput): boolean {
|
||||
return typeof match.home_score === 'number' && typeof match.away_score === 'number';
|
||||
}
|
||||
|
||||
export function matchStatusKind(match: MatchStatusInput, now: Date = new Date()): MatchStatusKind {
|
||||
const status = normalizedStatus(match);
|
||||
const kickoff = kickoffTimestamp(match);
|
||||
const nowMs = now.getTime();
|
||||
|
||||
if (/postponed|delayed|suspended|cancelled|canceled|abandoned/.test(status)) {
|
||||
return 'postponed';
|
||||
}
|
||||
|
||||
if (/finished|final|full-time|fulltime|ft|completed|closed/.test(status)) {
|
||||
return 'finished';
|
||||
}
|
||||
|
||||
if (/live|in-play|inplay|playing|ongoing|1st|2nd|first-half|second-half|halftime|half-time/.test(status)) {
|
||||
return 'live';
|
||||
}
|
||||
|
||||
if (kickoff === null) {
|
||||
return hasScore(match) ? 'finished' : 'unknown';
|
||||
}
|
||||
|
||||
if (kickoff > nowMs) {
|
||||
return 'upcoming';
|
||||
}
|
||||
|
||||
const elapsed = nowMs - kickoff;
|
||||
if (elapsed <= LIVE_WINDOW_MS) {
|
||||
return 'live';
|
||||
}
|
||||
|
||||
if (hasScore(match) || elapsed > RESULT_BACKFILL_GRACE_MS) {
|
||||
return hasScore(match) ? 'finished' : 'unknown';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
export function matchStatusLabel(match: MatchStatusInput, now: Date = new Date()): string {
|
||||
const kind = matchStatusKind(match, now);
|
||||
const score = hasScore(match) ? `${match.home_score}-${match.away_score}` : null;
|
||||
|
||||
if (kind === 'live') {
|
||||
return score ? `進行中 ${score}` : '進行中';
|
||||
}
|
||||
if (kind === 'finished') {
|
||||
return score ? `已完賽 ${score}` : '已完賽';
|
||||
}
|
||||
if (kind === 'postponed') {
|
||||
return '延期/中止';
|
||||
}
|
||||
if (kind === 'upcoming') {
|
||||
return '未開賽';
|
||||
}
|
||||
return '結果待回填';
|
||||
}
|
||||
|
||||
export function sortMatchesForProfessionalDisplay<T extends MatchStatusInput>(matches: T[], now: Date = new Date()): T[] {
|
||||
const rank: Record<MatchStatusKind, number> = {
|
||||
live: 0,
|
||||
upcoming: 1,
|
||||
unknown: 2,
|
||||
postponed: 3,
|
||||
finished: 4,
|
||||
};
|
||||
|
||||
return [...matches].sort((a, b) => {
|
||||
const kindA = matchStatusKind(a, now);
|
||||
const kindB = matchStatusKind(b, now);
|
||||
if (kindA !== kindB) {
|
||||
return rank[kindA] - rank[kindB];
|
||||
}
|
||||
|
||||
const timeA = kickoffTimestamp(a) ?? 0;
|
||||
const timeB = kickoffTimestamp(b) ?? 0;
|
||||
if (kindA === 'finished') {
|
||||
return timeB - timeA;
|
||||
}
|
||||
return timeA - timeB;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user