do to formatters
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
import { DashboardConsistencyFormatter } from '@/lib/formatters/DashboardConsistencyFormatter';
|
||||
import { DashboardCountFormatter } from '@/lib/formatters/DashboardCountFormatter';
|
||||
import { DashboardDateFormatter } from '@/lib/formatters/DashboardDateFormatter';
|
||||
import { DashboardLeaguePositionFormatter } from '@/lib/formatters/DashboardLeaguePositionFormatter';
|
||||
import { DashboardRankFormatter } from '@/lib/formatters/DashboardRankFormatter';
|
||||
import { RatingFormatter } from '@/lib/formatters/RatingFormatter';
|
||||
import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO';
|
||||
import type { DashboardViewData } from '@/lib/view-data/DashboardViewData';
|
||||
import { DashboardDateDisplay } from '@/lib/display-objects/DashboardDateDisplay';
|
||||
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
|
||||
import { DashboardRankDisplay } from '@/lib/display-objects/DashboardRankDisplay';
|
||||
import { DashboardConsistencyDisplay } from '@/lib/display-objects/DashboardConsistencyDisplay';
|
||||
import { DashboardCountDisplay } from '@/lib/display-objects/DashboardCountDisplay';
|
||||
import { DashboardLeaguePositionDisplay } from '@/lib/display-objects/DashboardLeaguePositionDisplay';
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
|
||||
export class DashboardViewDataBuilder {
|
||||
public static build(apiDto: DashboardOverviewDTO): DashboardViewData {
|
||||
@@ -17,21 +17,21 @@ export class DashboardViewDataBuilder {
|
||||
name: apiDto.currentDriver?.name || '',
|
||||
avatarUrl: apiDto.currentDriver?.avatarUrl || '',
|
||||
country: apiDto.currentDriver?.country || '',
|
||||
rating: apiDto.currentDriver ? RatingDisplay.format(apiDto.currentDriver.rating ?? 0) : '0.0',
|
||||
rank: apiDto.currentDriver ? DashboardRankDisplay.format(apiDto.currentDriver.globalRank ?? 0) : '0',
|
||||
totalRaces: apiDto.currentDriver ? DashboardCountDisplay.format(apiDto.currentDriver.totalRaces ?? 0) : '0',
|
||||
wins: apiDto.currentDriver ? DashboardCountDisplay.format(apiDto.currentDriver.wins ?? 0) : '0',
|
||||
podiums: apiDto.currentDriver ? DashboardCountDisplay.format(apiDto.currentDriver.podiums ?? 0) : '0',
|
||||
consistency: apiDto.currentDriver ? DashboardConsistencyDisplay.format(apiDto.currentDriver.consistency ?? 0) : '0%',
|
||||
rating: apiDto.currentDriver ? RatingFormatter.format(apiDto.currentDriver.rating ?? 0) : '0.0',
|
||||
rank: apiDto.currentDriver ? DashboardRankFormatter.format(apiDto.currentDriver.globalRank ?? 0) : '0',
|
||||
totalRaces: apiDto.currentDriver ? DashboardCountFormatter.format(apiDto.currentDriver.totalRaces ?? 0) : '0',
|
||||
wins: apiDto.currentDriver ? DashboardCountFormatter.format(apiDto.currentDriver.wins ?? 0) : '0',
|
||||
podiums: apiDto.currentDriver ? DashboardCountFormatter.format(apiDto.currentDriver.podiums ?? 0) : '0',
|
||||
consistency: apiDto.currentDriver ? DashboardConsistencyFormatter.format(apiDto.currentDriver.consistency ?? 0) : '0%',
|
||||
},
|
||||
nextRace: apiDto.nextRace ? DashboardViewDataBuilder.buildNextRace(apiDto.nextRace) : null,
|
||||
upcomingRaces: apiDto.upcomingRaces.map((race) => DashboardViewDataBuilder.buildRace(race)),
|
||||
leagueStandings: apiDto.leagueStandingsSummaries.map((standing) => ({
|
||||
leagueId: standing.leagueId,
|
||||
leagueName: standing.leagueName,
|
||||
position: DashboardLeaguePositionDisplay.format(standing.position),
|
||||
points: DashboardCountDisplay.format(standing.points),
|
||||
totalDrivers: DashboardCountDisplay.format(standing.totalDrivers),
|
||||
position: DashboardLeaguePositionFormatter.format(standing.position),
|
||||
points: DashboardCountFormatter.format(standing.points),
|
||||
totalDrivers: DashboardCountFormatter.format(standing.totalDrivers),
|
||||
})),
|
||||
feedItems: apiDto.feedSummary.items.map((item) => ({
|
||||
id: item.id,
|
||||
@@ -39,7 +39,7 @@ export class DashboardViewDataBuilder {
|
||||
headline: item.headline,
|
||||
body: item.body ?? undefined,
|
||||
timestamp: item.timestamp,
|
||||
formattedTime: DashboardDateDisplay.format(new Date(item.timestamp)).relative,
|
||||
formattedTime: DashboardDateFormatter.format(new Date(item.timestamp)).relative,
|
||||
ctaHref: item.ctaHref ?? undefined,
|
||||
ctaLabel: item.ctaLabel ?? undefined,
|
||||
})),
|
||||
@@ -49,8 +49,8 @@ export class DashboardViewDataBuilder {
|
||||
avatarUrl: friend.avatarUrl || '',
|
||||
country: friend.country,
|
||||
})),
|
||||
activeLeaguesCount: DashboardCountDisplay.format(apiDto.activeLeaguesCount),
|
||||
friendCount: DashboardCountDisplay.format(apiDto.friends.length),
|
||||
activeLeaguesCount: DashboardCountFormatter.format(apiDto.activeLeaguesCount),
|
||||
friendCount: DashboardCountFormatter.format(apiDto.friends.length),
|
||||
hasUpcomingRaces: apiDto.upcomingRaces.length > 0,
|
||||
hasLeagueStandings: apiDto.leagueStandingsSummaries.length > 0,
|
||||
hasFeedItems: apiDto.feedSummary.items.length > 0,
|
||||
@@ -59,7 +59,7 @@ export class DashboardViewDataBuilder {
|
||||
}
|
||||
|
||||
private static buildNextRace(race: NonNullable<DashboardOverviewDTO['nextRace']>) {
|
||||
const dateInfo = DashboardDateDisplay.format(new Date(race.scheduledAt));
|
||||
const dateInfo = DashboardDateFormatter.format(new Date(race.scheduledAt));
|
||||
return {
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
@@ -73,7 +73,7 @@ export class DashboardViewDataBuilder {
|
||||
}
|
||||
|
||||
private static buildRace(race: DashboardOverviewDTO['upcomingRaces'][number]) {
|
||||
const dateInfo = DashboardDateDisplay.format(new Date(race.scheduledAt));
|
||||
const dateInfo = DashboardDateFormatter.format(new Date(race.scheduledAt));
|
||||
return {
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
import { DateFormatter } from '@/lib/formatters/DateFormatter';
|
||||
import { FinishFormatter } from '@/lib/formatters/FinishFormatter';
|
||||
import { NumberFormatter } from '@/lib/formatters/NumberFormatter';
|
||||
import { PercentFormatter } from '@/lib/formatters/PercentFormatter';
|
||||
import { RatingFormatter } from '@/lib/formatters/RatingFormatter';
|
||||
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
|
||||
import type { DriverProfileViewData } from '@/lib/view-data/DriverProfileViewData';
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
|
||||
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
|
||||
import { FinishDisplay } from '@/lib/display-objects/FinishDisplay';
|
||||
import { PercentDisplay } from '@/lib/display-objects/PercentDisplay';
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
|
||||
export class DriverProfileViewDataBuilder {
|
||||
public static build(apiDto: GetDriverProfileOutputDTO): DriverProfileViewData {
|
||||
@@ -19,9 +19,9 @@ export class DriverProfileViewDataBuilder {
|
||||
avatarUrl: apiDto.currentDriver.avatarUrl || '',
|
||||
iracingId: typeof apiDto.currentDriver.iracingId === 'string' ? parseInt(apiDto.currentDriver.iracingId, 10) : (apiDto.currentDriver.iracingId ?? null),
|
||||
joinedAt: apiDto.currentDriver.joinedAt,
|
||||
joinedAtLabel: DateDisplay.formatMonthYear(apiDto.currentDriver.joinedAt),
|
||||
joinedAtLabel: DateFormatter.formatMonthYear(apiDto.currentDriver.joinedAt),
|
||||
rating: apiDto.currentDriver.rating ?? null,
|
||||
ratingLabel: RatingDisplay.format(apiDto.currentDriver.rating),
|
||||
ratingLabel: RatingFormatter.format(apiDto.currentDriver.rating),
|
||||
globalRank: apiDto.currentDriver.globalRank ?? null,
|
||||
globalRankLabel: apiDto.currentDriver.globalRank != null ? `#${apiDto.currentDriver.globalRank}` : '—',
|
||||
consistency: apiDto.currentDriver.consistency ?? null,
|
||||
@@ -30,27 +30,27 @@ export class DriverProfileViewDataBuilder {
|
||||
} : null,
|
||||
stats: apiDto.stats ? {
|
||||
totalRaces: apiDto.stats.totalRaces,
|
||||
totalRacesLabel: NumberDisplay.format(apiDto.stats.totalRaces),
|
||||
totalRacesLabel: NumberFormatter.format(apiDto.stats.totalRaces),
|
||||
wins: apiDto.stats.wins,
|
||||
winsLabel: NumberDisplay.format(apiDto.stats.wins),
|
||||
winsLabel: NumberFormatter.format(apiDto.stats.wins),
|
||||
podiums: apiDto.stats.podiums,
|
||||
podiumsLabel: NumberDisplay.format(apiDto.stats.podiums),
|
||||
podiumsLabel: NumberFormatter.format(apiDto.stats.podiums),
|
||||
dnfs: apiDto.stats.dnfs,
|
||||
dnfsLabel: NumberDisplay.format(apiDto.stats.dnfs),
|
||||
dnfsLabel: NumberFormatter.format(apiDto.stats.dnfs),
|
||||
avgFinish: apiDto.stats.avgFinish ?? null,
|
||||
avgFinishLabel: FinishDisplay.formatAverage(apiDto.stats.avgFinish),
|
||||
avgFinishLabel: FinishFormatter.formatAverage(apiDto.stats.avgFinish),
|
||||
bestFinish: apiDto.stats.bestFinish ?? null,
|
||||
bestFinishLabel: FinishDisplay.format(apiDto.stats.bestFinish),
|
||||
bestFinishLabel: FinishFormatter.format(apiDto.stats.bestFinish),
|
||||
worstFinish: apiDto.stats.worstFinish ?? null,
|
||||
worstFinishLabel: FinishDisplay.format(apiDto.stats.worstFinish),
|
||||
worstFinishLabel: FinishFormatter.format(apiDto.stats.worstFinish),
|
||||
finishRate: apiDto.stats.finishRate ?? null,
|
||||
winRate: apiDto.stats.winRate ?? null,
|
||||
podiumRate: apiDto.stats.podiumRate ?? null,
|
||||
percentile: apiDto.stats.percentile ?? null,
|
||||
rating: apiDto.stats.rating ?? null,
|
||||
ratingLabel: RatingDisplay.format(apiDto.stats.rating),
|
||||
ratingLabel: RatingFormatter.format(apiDto.stats.rating),
|
||||
consistency: apiDto.stats.consistency ?? null,
|
||||
consistencyLabel: PercentDisplay.formatWhole(apiDto.stats.consistency),
|
||||
consistencyLabel: PercentFormatter.formatWhole(apiDto.stats.consistency),
|
||||
overallRank: apiDto.stats.overallRank ?? null,
|
||||
} : null,
|
||||
finishDistribution: apiDto.finishDistribution ? {
|
||||
@@ -67,7 +67,7 @@ export class DriverProfileViewDataBuilder {
|
||||
teamTag: m.teamTag ?? null,
|
||||
role: m.role,
|
||||
joinedAt: m.joinedAt,
|
||||
joinedAtLabel: DateDisplay.formatMonthYear(m.joinedAt),
|
||||
joinedAtLabel: DateFormatter.formatMonthYear(m.joinedAt),
|
||||
isCurrent: m.isCurrent,
|
||||
})),
|
||||
socialSummary: {
|
||||
@@ -93,7 +93,7 @@ export class DriverProfileViewDataBuilder {
|
||||
rarity: a.rarity,
|
||||
rarityLabel: a.rarity,
|
||||
earnedAt: a.earnedAt,
|
||||
earnedAtLabel: DateDisplay.formatShort(a.earnedAt),
|
||||
earnedAtLabel: DateFormatter.formatShort(a.earnedAt),
|
||||
})),
|
||||
racingStyle: apiDto.extendedProfile.racingStyle,
|
||||
favoriteTrack: apiDto.extendedProfile.favoriteTrack,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
import { MedalFormatter } from '@/lib/formatters/MedalFormatter';
|
||||
import { WinRateFormatter } from '@/lib/formatters/WinRateFormatter';
|
||||
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
|
||||
import type { DriverRankingsViewData } from '@/lib/view-data/DriverRankingsViewData';
|
||||
import { WinRateDisplay } from '@/lib/display-objects/WinRateDisplay';
|
||||
import { MedalDisplay } from '@/lib/display-objects/MedalDisplay';
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
|
||||
export class DriverRankingsViewDataBuilder {
|
||||
public static build(apiDto: DriverLeaderboardItemDTO[]): DriverRankingsViewData {
|
||||
@@ -31,9 +31,9 @@ export class DriverRankingsViewDataBuilder {
|
||||
podiums: driver.podiums,
|
||||
rank: driver.rank,
|
||||
avatarUrl: driver.avatarUrl || '',
|
||||
winRate: WinRateDisplay.calculate(driver.racesCompleted, driver.wins),
|
||||
medalBg: MedalDisplay.getBg(driver.rank),
|
||||
medalColor: MedalDisplay.getColor(driver.rank),
|
||||
winRate: WinRateFormatter.calculate(driver.racesCompleted, driver.wins),
|
||||
medalBg: MedalFormatter.getBg(driver.rank),
|
||||
medalColor: MedalFormatter.getColor(driver.rank),
|
||||
})),
|
||||
podium: apiDto.slice(0, 3).map((driver, index) => {
|
||||
const positions = [2, 1, 3]; // Display order: 2nd, 1st, 3rd
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
import { NumberFormatter } from '@/lib/formatters/NumberFormatter';
|
||||
import { RatingFormatter } from '@/lib/formatters/RatingFormatter';
|
||||
import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO';
|
||||
import type { DriversViewData } from '@/lib/view-data/DriversViewData';
|
||||
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
|
||||
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
|
||||
export class DriversViewDataBuilder {
|
||||
public static build(apiDto: DriversLeaderboardDTO): DriversViewData {
|
||||
@@ -13,7 +13,7 @@ export class DriversViewDataBuilder {
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
rating: driver.rating,
|
||||
ratingLabel: RatingDisplay.format(driver.rating),
|
||||
ratingLabel: RatingFormatter.format(driver.rating),
|
||||
skillLevel: driver.skillLevel,
|
||||
category: driver.category ?? undefined,
|
||||
nationality: driver.nationality,
|
||||
@@ -25,12 +25,12 @@ export class DriversViewDataBuilder {
|
||||
avatarUrl: driver.avatarUrl ?? undefined,
|
||||
})),
|
||||
totalRaces: apiDto.totalRaces,
|
||||
totalRacesLabel: NumberDisplay.format(apiDto.totalRaces),
|
||||
totalRacesLabel: NumberFormatter.format(apiDto.totalRaces),
|
||||
totalWins: apiDto.totalWins,
|
||||
totalWinsLabel: NumberDisplay.format(apiDto.totalWins),
|
||||
totalWinsLabel: NumberFormatter.format(apiDto.totalWins),
|
||||
activeCount: apiDto.activeCount,
|
||||
activeCountLabel: NumberDisplay.format(apiDto.activeCount),
|
||||
totalDriversLabel: NumberDisplay.format(apiDto.drivers.length),
|
||||
activeCountLabel: NumberFormatter.format(apiDto.activeCount),
|
||||
totalDriversLabel: NumberFormatter.format(apiDto.drivers.length),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import type { HealthDTO } from '@/lib/types/generated/HealthDTO';
|
||||
import type { HealthViewData, HealthStatus, HealthMetrics, HealthComponent, HealthAlert } from '@/lib/view-data/HealthViewData';
|
||||
import { HealthStatusDisplay } from '@/lib/display-objects/HealthStatusDisplay';
|
||||
import { HealthMetricDisplay } from '@/lib/display-objects/HealthMetricDisplay';
|
||||
import { HealthComponentDisplay } from '@/lib/display-objects/HealthComponentDisplay';
|
||||
import { HealthAlertDisplay } from '@/lib/display-objects/HealthAlertDisplay';
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
import { HealthAlertFormatter } from '@/lib/formatters/HealthAlertFormatter';
|
||||
import { HealthComponentFormatter } from '@/lib/formatters/HealthComponentFormatter';
|
||||
import { HealthMetricFormatter } from '@/lib/formatters/HealthMetricFormatter';
|
||||
import { HealthStatusFormatter } from '@/lib/formatters/HealthStatusFormatter';
|
||||
import type { HealthDTO } from '@/lib/types/generated/HealthDTO';
|
||||
import type { HealthAlert, HealthComponent, HealthMetrics, HealthStatus, HealthViewData } from '@/lib/view-data/HealthViewData';
|
||||
|
||||
export class HealthViewDataBuilder {
|
||||
public static build(apiDto: HealthDTO): HealthViewData {
|
||||
@@ -17,37 +17,37 @@ export class HealthViewDataBuilder {
|
||||
const overallStatus: HealthStatus = {
|
||||
status: apiDto.status,
|
||||
timestamp: apiDto.timestamp,
|
||||
formattedTimestamp: HealthStatusDisplay.formatTimestamp(apiDto.timestamp),
|
||||
relativeTime: HealthStatusDisplay.formatRelativeTime(apiDto.timestamp),
|
||||
statusLabel: HealthStatusDisplay.formatStatusLabel(apiDto.status),
|
||||
statusColor: HealthStatusDisplay.formatStatusColor(apiDto.status),
|
||||
statusIcon: HealthStatusDisplay.formatStatusIcon(apiDto.status),
|
||||
formattedTimestamp: HealthStatusFormatter.formatTimestamp(apiDto.timestamp),
|
||||
relativeTime: HealthStatusFormatter.formatRelativeTime(apiDto.timestamp),
|
||||
statusLabel: HealthStatusFormatter.formatStatusLabel(apiDto.status),
|
||||
statusColor: HealthStatusFormatter.formatStatusColor(apiDto.status),
|
||||
statusIcon: HealthStatusFormatter.formatStatusIcon(apiDto.status),
|
||||
};
|
||||
|
||||
// Build metrics
|
||||
const metrics: HealthMetrics = {
|
||||
uptime: HealthMetricDisplay.formatUptime(apiDto.uptime),
|
||||
responseTime: HealthMetricDisplay.formatResponseTime(apiDto.responseTime),
|
||||
errorRate: HealthMetricDisplay.formatErrorRate(apiDto.errorRate),
|
||||
uptime: HealthMetricFormatter.formatUptime(apiDto.uptime),
|
||||
responseTime: HealthMetricFormatter.formatResponseTime(apiDto.responseTime),
|
||||
errorRate: HealthMetricFormatter.formatErrorRate(apiDto.errorRate),
|
||||
lastCheck: apiDto.lastCheck || lastUpdated,
|
||||
formattedLastCheck: HealthMetricDisplay.formatTimestamp(apiDto.lastCheck || lastUpdated),
|
||||
formattedLastCheck: HealthMetricFormatter.formatTimestamp(apiDto.lastCheck || lastUpdated),
|
||||
checksPassed: apiDto.checksPassed || 0,
|
||||
checksFailed: apiDto.checksFailed || 0,
|
||||
totalChecks: (apiDto.checksPassed || 0) + (apiDto.checksFailed || 0),
|
||||
successRate: HealthMetricDisplay.formatSuccessRate(apiDto.checksPassed, apiDto.checksFailed),
|
||||
successRate: HealthMetricFormatter.formatSuccessRate(apiDto.checksPassed, apiDto.checksFailed),
|
||||
};
|
||||
|
||||
// Build components
|
||||
const components: HealthComponent[] = (apiDto.components || []).map((component) => ({
|
||||
name: component.name,
|
||||
status: component.status,
|
||||
statusLabel: HealthComponentDisplay.formatStatusLabel(component.status),
|
||||
statusColor: HealthComponentDisplay.formatStatusColor(component.status),
|
||||
statusIcon: HealthComponentDisplay.formatStatusIcon(component.status),
|
||||
statusLabel: HealthComponentFormatter.formatStatusLabel(component.status),
|
||||
statusColor: HealthComponentFormatter.formatStatusColor(component.status),
|
||||
statusIcon: HealthComponentFormatter.formatStatusIcon(component.status),
|
||||
lastCheck: component.lastCheck || lastUpdated,
|
||||
formattedLastCheck: HealthComponentDisplay.formatTimestamp(component.lastCheck || lastUpdated),
|
||||
responseTime: HealthMetricDisplay.formatResponseTime(component.responseTime),
|
||||
errorRate: HealthMetricDisplay.formatErrorRate(component.errorRate),
|
||||
formattedLastCheck: HealthComponentFormatter.formatTimestamp(component.lastCheck || lastUpdated),
|
||||
responseTime: HealthMetricFormatter.formatResponseTime(component.responseTime),
|
||||
errorRate: HealthMetricFormatter.formatErrorRate(component.errorRate),
|
||||
}));
|
||||
|
||||
// Build alerts
|
||||
@@ -57,10 +57,10 @@ export class HealthViewDataBuilder {
|
||||
title: alert.title,
|
||||
message: alert.message,
|
||||
timestamp: alert.timestamp,
|
||||
formattedTimestamp: HealthAlertDisplay.formatTimestamp(alert.timestamp),
|
||||
relativeTime: HealthAlertDisplay.formatRelativeTime(alert.timestamp),
|
||||
severity: HealthAlertDisplay.formatSeverity(alert.type),
|
||||
severityColor: HealthAlertDisplay.formatSeverityColor(alert.type),
|
||||
formattedTimestamp: HealthAlertFormatter.formatTimestamp(alert.timestamp),
|
||||
relativeTime: HealthAlertFormatter.formatRelativeTime(alert.timestamp),
|
||||
severity: HealthAlertFormatter.formatSeverity(alert.type),
|
||||
severityColor: HealthAlertFormatter.formatSeverityColor(alert.type),
|
||||
}));
|
||||
|
||||
// Calculate derived fields
|
||||
@@ -77,7 +77,7 @@ export class HealthViewDataBuilder {
|
||||
hasDegradedComponents,
|
||||
hasErrorComponents,
|
||||
lastUpdated,
|
||||
formattedLastUpdated: HealthStatusDisplay.formatTimestamp(lastUpdated),
|
||||
formattedLastUpdated: HealthStatusFormatter.formatTimestamp(lastUpdated),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
import { DashboardDateFormatter } from '@/lib/formatters/DashboardDateFormatter';
|
||||
import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO';
|
||||
import type { HomeViewData } from '@/lib/view-data/HomeViewData';
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
import { DashboardDateDisplay } from '@/lib/display-objects/DashboardDateDisplay';
|
||||
|
||||
export class HomeViewDataBuilder {
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ export class HomeViewDataBuilder {
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
formattedDate: DashboardDateDisplay.format(new Date(race.scheduledAt)).date,
|
||||
formattedDate: DashboardDateFormatter.format(new Date(race.scheduledAt)).date,
|
||||
})),
|
||||
topLeagues: (apiDto.leagueStandingsSummaries || []).map(league => ({
|
||||
id: league.leagueId,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
|
||||
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
|
||||
import type { LeagueRosterAdminViewData, RosterMemberData, JoinRequestData } from '@/lib/view-data/LeagueRosterAdminViewData';
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
||||
import { DateFormatter } from '@/lib/formatters/DateFormatter';
|
||||
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
|
||||
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
|
||||
import type { JoinRequestData, LeagueRosterAdminViewData, RosterMemberData } from '@/lib/view-data/LeagueRosterAdminViewData';
|
||||
|
||||
type LeagueRosterAdminInputDTO = {
|
||||
leagueId: string;
|
||||
@@ -23,7 +23,7 @@ export class LeagueRosterAdminViewDataBuilder {
|
||||
},
|
||||
role: member.role,
|
||||
joinedAt: member.joinedAt,
|
||||
formattedJoinedAt: DateDisplay.formatShort(member.joinedAt),
|
||||
formattedJoinedAt: DateFormatter.formatShort(member.joinedAt),
|
||||
}));
|
||||
|
||||
// Transform join requests
|
||||
@@ -34,7 +34,7 @@ export class LeagueRosterAdminViewDataBuilder {
|
||||
name: (req as { driver?: { name?: string } }).driver?.name || 'Unknown Driver',
|
||||
},
|
||||
requestedAt: req.requestedAt,
|
||||
formattedRequestedAt: DateDisplay.formatShort(req.requestedAt),
|
||||
formattedRequestedAt: DateFormatter.formatShort(req.requestedAt),
|
||||
message: req.message ?? undefined,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { StatusDisplay } from '@/lib/display-objects/StatusDisplay';
|
||||
import { DateFormatter } from '@/lib/formatters/DateFormatter';
|
||||
import { StatusFormatter } from '@/lib/formatters/StatusFormatter';
|
||||
import { LeagueSponsorshipsApiDto } from '@/lib/types/tbd/LeagueSponsorshipsApiDto';
|
||||
import { LeagueSponsorshipsViewData } from '@/lib/view-data/LeagueSponsorshipsViewData';
|
||||
|
||||
@@ -19,8 +19,8 @@ export class LeagueSponsorshipsViewDataBuilder implements ViewDataBuilder<any, a
|
||||
sponsorshipSlots: apiDto.sponsorshipSlots,
|
||||
sponsorshipRequests: apiDto.sponsorshipRequests.map(r => ({
|
||||
...r,
|
||||
formattedRequestedAt: DateDisplay.formatShort(r.requestedAt),
|
||||
statusLabel: StatusDisplay.protestStatus(r.status), // Reusing protest status for now
|
||||
formattedRequestedAt: DateFormatter.formatShort(r.requestedAt),
|
||||
statusLabel: StatusFormatter.protestStatus(r.status), // Reusing protest status for now
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { mediaConfig } from '@/lib/config/mediaConfig';
|
||||
import { CountryFlagFormatter } from '@/lib/formatters/CountryFlagFormatter';
|
||||
import { DateFormatter } from '@/lib/formatters/DateFormatter';
|
||||
import { FinishFormatter } from '@/lib/formatters/FinishFormatter';
|
||||
import { NumberFormatter } from '@/lib/formatters/NumberFormatter';
|
||||
import { PercentFormatter } from '@/lib/formatters/PercentFormatter';
|
||||
import { RatingFormatter } from '@/lib/formatters/RatingFormatter';
|
||||
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
|
||||
import type { ProfileViewData } from '@/lib/view-data/ProfileViewData';
|
||||
import { mediaConfig } from '@/lib/config/mediaConfig';
|
||||
import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { FinishDisplay } from '@/lib/display-objects/FinishDisplay';
|
||||
import { PercentDisplay } from '@/lib/display-objects/PercentDisplay';
|
||||
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
|
||||
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
|
||||
|
||||
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
|
||||
|
||||
@@ -24,7 +24,7 @@ export class ProfileViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
id: '',
|
||||
name: '',
|
||||
countryCode: '',
|
||||
countryFlag: CountryFlagDisplay.fromCountryCode(null).toString(),
|
||||
countryFlag: CountryFlagFormatter.fromCountryCode(null).toString(),
|
||||
avatarUrl: mediaConfig.avatars.defaultFallback,
|
||||
bio: null,
|
||||
iracingId: null,
|
||||
@@ -45,25 +45,25 @@ export class ProfileViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
countryCode: driver.country,
|
||||
countryFlag: CountryFlagDisplay.fromCountryCode(driver.country).toString(),
|
||||
countryFlag: CountryFlagFormatter.fromCountryCode(driver.country).toString(),
|
||||
avatarUrl: driver.avatarUrl || mediaConfig.avatars.defaultFallback,
|
||||
bio: driver.bio || null,
|
||||
iracingId: driver.iracingId ? String(driver.iracingId) : null,
|
||||
joinedAtLabel: DateDisplay.formatMonthYear(driver.joinedAt),
|
||||
joinedAtLabel: DateFormatter.formatMonthYear(driver.joinedAt),
|
||||
},
|
||||
stats: stats
|
||||
? {
|
||||
ratingLabel: RatingDisplay.format(stats.rating),
|
||||
ratingLabel: RatingFormatter.format(stats.rating),
|
||||
globalRankLabel: driver.globalRank != null ? `#${driver.globalRank}` : '—',
|
||||
totalRacesLabel: NumberDisplay.format(stats.totalRaces),
|
||||
winsLabel: NumberDisplay.format(stats.wins),
|
||||
podiumsLabel: NumberDisplay.format(stats.podiums),
|
||||
dnfsLabel: NumberDisplay.format(stats.dnfs),
|
||||
bestFinishLabel: FinishDisplay.format(stats.bestFinish),
|
||||
worstFinishLabel: FinishDisplay.format(stats.worstFinish),
|
||||
avgFinishLabel: FinishDisplay.formatAverage(stats.avgFinish),
|
||||
consistencyLabel: PercentDisplay.formatWhole(stats.consistency),
|
||||
percentileLabel: PercentDisplay.format(stats.percentile),
|
||||
totalRacesLabel: NumberFormatter.format(stats.totalRaces),
|
||||
winsLabel: NumberFormatter.format(stats.wins),
|
||||
podiumsLabel: NumberFormatter.format(stats.podiums),
|
||||
dnfsLabel: NumberFormatter.format(stats.dnfs),
|
||||
bestFinishLabel: FinishFormatter.format(stats.bestFinish),
|
||||
worstFinishLabel: FinishFormatter.format(stats.worstFinish),
|
||||
avgFinishLabel: FinishFormatter.formatAverage(stats.avgFinish),
|
||||
consistencyLabel: PercentFormatter.formatWhole(stats.consistency),
|
||||
percentileLabel: PercentFormatter.format(stats.percentile),
|
||||
}
|
||||
: null,
|
||||
teamMemberships: apiDto.teamMemberships.map((m) => ({
|
||||
@@ -71,7 +71,7 @@ export class ProfileViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
teamName: m.teamName,
|
||||
teamTag: m.teamTag || null,
|
||||
roleLabel: m.role,
|
||||
joinedAtLabel: DateDisplay.formatMonthYear(m.joinedAt),
|
||||
joinedAtLabel: DateFormatter.formatMonthYear(m.joinedAt),
|
||||
href: `/teams/${m.teamId}`,
|
||||
})),
|
||||
extendedProfile: extended
|
||||
@@ -92,18 +92,18 @@ export class ProfileViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
description: a.description,
|
||||
earnedAtLabel: DateDisplay.formatShort(a.earnedAt),
|
||||
earnedAtLabel: DateFormatter.formatShort(a.earnedAt),
|
||||
icon: a.icon as any,
|
||||
rarityLabel: a.rarity,
|
||||
})),
|
||||
friends: socialSummary.friends.slice(0, 8).map((f) => ({
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
countryFlag: CountryFlagDisplay.fromCountryCode(f.country).toString(),
|
||||
countryFlag: CountryFlagFormatter.fromCountryCode(f.country).toString(),
|
||||
avatarUrl: f.avatarUrl || mediaConfig.avatars.defaultFallback,
|
||||
href: `/drivers/${f.id}`,
|
||||
})),
|
||||
friendsCountLabel: NumberDisplay.format(socialSummary.friendsCount),
|
||||
friendsCountLabel: NumberFormatter.format(socialSummary.friendsCount),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DateFormatter } from '@/lib/formatters/DateFormatter';
|
||||
import { RaceStatusFormatter } from '@/lib/formatters/RaceStatusFormatter';
|
||||
import { RelativeTimeFormatter } from '@/lib/formatters/RelativeTimeFormatter';
|
||||
import type { RacesPageDataDTO } from '@/lib/types/generated/RacesPageDataDTO';
|
||||
import type { RacesViewData, RaceViewData } from '@/lib/view-data/RacesViewData';
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { RaceStatusDisplay } from '@/lib/display-objects/RaceStatusDisplay';
|
||||
import { RelativeTimeDisplay } from '@/lib/display-objects/RelativeTimeDisplay';
|
||||
|
||||
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
|
||||
|
||||
@@ -19,13 +19,13 @@ export class RacesViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
scheduledAt: race.scheduledAt,
|
||||
scheduledAtLabel: DateDisplay.formatShort(race.scheduledAt),
|
||||
timeLabel: DateDisplay.formatTime(race.scheduledAt),
|
||||
relativeTimeLabel: RelativeTimeDisplay.format(race.scheduledAt, now),
|
||||
scheduledAtLabel: DateFormatter.formatShort(race.scheduledAt),
|
||||
timeLabel: DateFormatter.formatTime(race.scheduledAt),
|
||||
relativeTimeLabel: RelativeTimeFormatter.format(race.scheduledAt, now),
|
||||
status: race.status as RaceViewData['status'],
|
||||
statusLabel: RaceStatusDisplay.getLabel(race.status),
|
||||
statusVariant: RaceStatusDisplay.getVariant(race.status),
|
||||
statusIconName: RaceStatusDisplay.getIconName(race.status),
|
||||
statusLabel: RaceStatusFormatter.getLabel(race.status),
|
||||
statusVariant: RaceStatusFormatter.getVariant(race.status),
|
||||
statusIconName: RaceStatusFormatter.getIconName(race.status),
|
||||
sessionType: 'Race',
|
||||
leagueId: race.leagueId,
|
||||
leagueName: race.leagueName,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DateFormatter } from '@/lib/formatters/DateFormatter';
|
||||
import { LeagueFormatter } from '@/lib/formatters/LeagueFormatter';
|
||||
import { MemberFormatter } from '@/lib/formatters/MemberFormatter';
|
||||
import { NumberFormatter } from '@/lib/formatters/NumberFormatter';
|
||||
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
|
||||
import type { TeamDetailViewData, TeamDetailData, TeamMemberData, SponsorMetric, TeamTab } from '@/lib/view-data/TeamDetailViewData';
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { MemberDisplay } from '@/lib/display-objects/MemberDisplay';
|
||||
import { LeagueDisplay } from '@/lib/display-objects/LeagueDisplay';
|
||||
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
|
||||
import type { SponsorMetric, TeamDetailData, TeamDetailViewData, TeamMemberData, TeamTab } from '@/lib/view-data/TeamDetailViewData';
|
||||
|
||||
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
|
||||
|
||||
@@ -21,7 +21,7 @@ export class TeamDetailViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
ownerId: apiDto.team.ownerId,
|
||||
leagues: (apiDto.team as any).leagues || [],
|
||||
createdAt: apiDto.team.createdAt,
|
||||
foundedDateLabel: apiDto.team.createdAt ? DateDisplay.formatMonthYear(apiDto.team.createdAt) : 'Unknown',
|
||||
foundedDateLabel: apiDto.team.createdAt ? DateFormatter.formatMonthYear(apiDto.team.createdAt) : 'Unknown',
|
||||
specialization: (apiDto.team as any).specialization || null,
|
||||
region: (apiDto.team as any).region || null,
|
||||
languages: (apiDto.team as any).languages || [],
|
||||
@@ -35,7 +35,7 @@ export class TeamDetailViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
driverName: membership.driverName,
|
||||
role: membership.role,
|
||||
joinedAt: membership.joinedAt,
|
||||
joinedAtLabel: DateDisplay.formatShort(membership.joinedAt),
|
||||
joinedAtLabel: DateFormatter.formatShort(membership.joinedAt),
|
||||
isActive: membership.isActive,
|
||||
avatarUrl: membership.avatarUrl,
|
||||
}));
|
||||
@@ -51,19 +51,19 @@ export class TeamDetailViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
{
|
||||
icon: 'users',
|
||||
label: 'Members',
|
||||
value: NumberDisplay.format(memberships.length),
|
||||
value: NumberFormatter.format(memberships.length),
|
||||
color: 'text-primary-blue',
|
||||
},
|
||||
{
|
||||
icon: 'zap',
|
||||
label: 'Est. Reach',
|
||||
value: NumberDisplay.format(memberships.length * 15),
|
||||
value: NumberFormatter.format(memberships.length * 15),
|
||||
color: 'text-purple-400',
|
||||
},
|
||||
{
|
||||
icon: 'calendar',
|
||||
label: 'Races',
|
||||
value: NumberDisplay.format(leagueCount),
|
||||
value: NumberFormatter.format(leagueCount),
|
||||
color: 'text-neon-aqua',
|
||||
},
|
||||
{
|
||||
@@ -89,8 +89,8 @@ export class TeamDetailViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
isAdmin,
|
||||
teamMetrics,
|
||||
tabs,
|
||||
memberCountLabel: MemberDisplay.formatCount(memberships.length),
|
||||
leagueCountLabel: LeagueDisplay.formatCount(leagueCount),
|
||||
memberCountLabel: MemberFormatter.formatCount(memberships.length),
|
||||
leagueCountLabel: LeagueFormatter.formatCount(leagueCount),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NumberFormatter } from '@/lib/formatters/NumberFormatter';
|
||||
import { RatingFormatter } from '@/lib/formatters/RatingFormatter';
|
||||
import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
|
||||
import type { TeamsViewData, TeamSummaryData } from '@/lib/view-data/TeamsViewData';
|
||||
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
|
||||
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
|
||||
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
|
||||
import type { TeamSummaryData, TeamsViewData } from '@/lib/view-data/TeamsViewData';
|
||||
|
||||
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
|
||||
|
||||
@@ -17,10 +17,10 @@ export class TeamsViewDataBuilder implements ViewDataBuilder<any, any> {
|
||||
teamName: team.name,
|
||||
memberCount: team.memberCount,
|
||||
logoUrl: team.logoUrl || '',
|
||||
ratingLabel: RatingDisplay.format(team.rating),
|
||||
ratingLabel: RatingFormatter.format(team.rating),
|
||||
ratingValue: team.rating || 0,
|
||||
winsLabel: NumberDisplay.format(team.totalWins || 0),
|
||||
racesLabel: NumberDisplay.format(team.totalRaces || 0),
|
||||
winsLabel: NumberFormatter.format(team.totalWins || 0),
|
||||
racesLabel: NumberFormatter.format(team.totalRaces || 0),
|
||||
region: team.region || '',
|
||||
isRecruiting: team.isRecruiting,
|
||||
category: team.category || '',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LeagueWizardValidationMessages } from '@/lib/formatters/LeagueWizardValidationMessages';
|
||||
import type { CreateLeagueInputDTO } from '@/lib/types/generated/CreateLeagueInputDTO';
|
||||
import { LeagueWizardValidationMessages } from '@/lib/display-objects/LeagueWizardValidationMessages';
|
||||
|
||||
export type WizardStep = 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* DisplayObject contract
|
||||
* Formatter contract
|
||||
*
|
||||
* Deterministic, reusable, UI-only formatting/mapping logic.
|
||||
*
|
||||
@@ -12,18 +12,11 @@
|
||||
* - No business rules
|
||||
*/
|
||||
|
||||
export interface DisplayObject {
|
||||
export interface Formatter {
|
||||
/**
|
||||
* Format or map the display object
|
||||
*
|
||||
* @returns Primitive values only (strings, numbers, booleans)
|
||||
*/
|
||||
format(): unknown;
|
||||
|
||||
/**
|
||||
* Optional: Get multiple display variants
|
||||
*
|
||||
* Allows a single DisplayObject to expose multiple presentation formats
|
||||
*/
|
||||
variants?(): Record<string, unknown>;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { DashboardCountDisplay } from './DashboardCountDisplay';
|
||||
|
||||
describe('DashboardCountDisplay', () => {
|
||||
describe('happy paths', () => {
|
||||
it('should format positive numbers correctly', () => {
|
||||
expect(DashboardCountDisplay.format(0)).toBe('0');
|
||||
expect(DashboardCountDisplay.format(1)).toBe('1');
|
||||
expect(DashboardCountDisplay.format(100)).toBe('100');
|
||||
expect(DashboardCountDisplay.format(1000)).toBe('1000');
|
||||
});
|
||||
|
||||
it('should handle null values', () => {
|
||||
expect(DashboardCountDisplay.format(null)).toBe('0');
|
||||
});
|
||||
|
||||
it('should handle undefined values', () => {
|
||||
expect(DashboardCountDisplay.format(undefined)).toBe('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle negative numbers', () => {
|
||||
expect(DashboardCountDisplay.format(-1)).toBe('-1');
|
||||
expect(DashboardCountDisplay.format(-100)).toBe('-100');
|
||||
});
|
||||
|
||||
it('should handle large numbers', () => {
|
||||
expect(DashboardCountDisplay.format(999999)).toBe('999999');
|
||||
expect(DashboardCountDisplay.format(1000000)).toBe('1000000');
|
||||
});
|
||||
|
||||
it('should handle decimal numbers', () => {
|
||||
expect(DashboardCountDisplay.format(1.5)).toBe('1.5');
|
||||
expect(DashboardCountDisplay.format(100.99)).toBe('100.99');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,369 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { DashboardViewDataBuilder } from '../builders/view-data/DashboardViewDataBuilder';
|
||||
import { DashboardDateDisplay } from './DashboardDateDisplay';
|
||||
import { DashboardCountDisplay } from './DashboardCountDisplay';
|
||||
import { DashboardRankDisplay } from './DashboardRankDisplay';
|
||||
import { DashboardConsistencyDisplay } from './DashboardConsistencyDisplay';
|
||||
import { DashboardLeaguePositionDisplay } from './DashboardLeaguePositionDisplay';
|
||||
import { RatingDisplay } from './RatingDisplay';
|
||||
import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO';
|
||||
|
||||
describe('Dashboard View Data - Cross-Component Consistency', () => {
|
||||
describe('common patterns', () => {
|
||||
it('should all use consistent formatting for numeric values', () => {
|
||||
const dashboardDTO: DashboardOverviewDTO = {
|
||||
currentDriver: {
|
||||
id: 'driver-123',
|
||||
name: 'John Doe',
|
||||
country: 'USA',
|
||||
rating: 1234.56,
|
||||
globalRank: 42,
|
||||
totalRaces: 150,
|
||||
wins: 25,
|
||||
podiums: 60,
|
||||
consistency: 85,
|
||||
},
|
||||
myUpcomingRaces: [],
|
||||
otherUpcomingRaces: [],
|
||||
upcomingRaces: [],
|
||||
activeLeaguesCount: 3,
|
||||
nextRace: null,
|
||||
recentResults: [],
|
||||
leagueStandingsSummaries: [
|
||||
{
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'Test League',
|
||||
position: 5,
|
||||
totalDrivers: 50,
|
||||
points: 1250,
|
||||
},
|
||||
],
|
||||
feedSummary: {
|
||||
notificationCount: 0,
|
||||
items: [],
|
||||
},
|
||||
friends: [
|
||||
{ id: 'friend-1', name: 'Alice', country: 'UK' },
|
||||
{ id: 'friend-2', name: 'Bob', country: 'Germany' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = DashboardViewDataBuilder.build(dashboardDTO);
|
||||
|
||||
// All numeric values should be formatted as strings
|
||||
expect(typeof result.currentDriver.rating).toBe('string');
|
||||
expect(typeof result.currentDriver.rank).toBe('string');
|
||||
expect(typeof result.currentDriver.totalRaces).toBe('string');
|
||||
expect(typeof result.currentDriver.wins).toBe('string');
|
||||
expect(typeof result.currentDriver.podiums).toBe('string');
|
||||
expect(typeof result.currentDriver.consistency).toBe('string');
|
||||
expect(typeof result.activeLeaguesCount).toBe('string');
|
||||
expect(typeof result.friendCount).toBe('string');
|
||||
expect(typeof result.leagueStandings[0].position).toBe('string');
|
||||
expect(typeof result.leagueStandings[0].points).toBe('string');
|
||||
expect(typeof result.leagueStandings[0].totalDrivers).toBe('string');
|
||||
});
|
||||
|
||||
it('should all handle missing data gracefully', () => {
|
||||
const dashboardDTO: DashboardOverviewDTO = {
|
||||
myUpcomingRaces: [],
|
||||
otherUpcomingRaces: [],
|
||||
upcomingRaces: [],
|
||||
activeLeaguesCount: 0,
|
||||
nextRace: null,
|
||||
recentResults: [],
|
||||
leagueStandingsSummaries: [],
|
||||
feedSummary: {
|
||||
notificationCount: 0,
|
||||
items: [],
|
||||
},
|
||||
friends: [],
|
||||
};
|
||||
|
||||
const result = DashboardViewDataBuilder.build(dashboardDTO);
|
||||
|
||||
// All fields should have safe defaults
|
||||
expect(result.currentDriver.name).toBe('');
|
||||
expect(result.currentDriver.avatarUrl).toBe('');
|
||||
expect(result.currentDriver.country).toBe('');
|
||||
expect(result.currentDriver.rating).toBe('0.0');
|
||||
expect(result.currentDriver.rank).toBe('0');
|
||||
expect(result.currentDriver.totalRaces).toBe('0');
|
||||
expect(result.currentDriver.wins).toBe('0');
|
||||
expect(result.currentDriver.podiums).toBe('0');
|
||||
expect(result.currentDriver.consistency).toBe('0%');
|
||||
expect(result.nextRace).toBeNull();
|
||||
expect(result.upcomingRaces).toEqual([]);
|
||||
expect(result.leagueStandings).toEqual([]);
|
||||
expect(result.feedItems).toEqual([]);
|
||||
expect(result.friends).toEqual([]);
|
||||
expect(result.activeLeaguesCount).toBe('0');
|
||||
expect(result.friendCount).toBe('0');
|
||||
});
|
||||
|
||||
it('should all preserve ISO timestamps for serialization', () => {
|
||||
const now = new Date();
|
||||
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
||||
const feedTimestamp = new Date(now.getTime() - 30 * 60 * 1000);
|
||||
|
||||
const dashboardDTO: DashboardOverviewDTO = {
|
||||
myUpcomingRaces: [],
|
||||
otherUpcomingRaces: [],
|
||||
upcomingRaces: [],
|
||||
activeLeaguesCount: 1,
|
||||
nextRace: {
|
||||
id: 'race-1',
|
||||
track: 'Spa',
|
||||
car: 'Porsche',
|
||||
scheduledAt: futureDate.toISOString(),
|
||||
status: 'scheduled',
|
||||
isMyLeague: true,
|
||||
},
|
||||
recentResults: [],
|
||||
leagueStandingsSummaries: [],
|
||||
feedSummary: {
|
||||
notificationCount: 1,
|
||||
items: [
|
||||
{
|
||||
id: 'feed-1',
|
||||
type: 'notification',
|
||||
headline: 'Test',
|
||||
timestamp: feedTimestamp.toISOString(),
|
||||
},
|
||||
],
|
||||
},
|
||||
friends: [],
|
||||
};
|
||||
|
||||
const result = DashboardViewDataBuilder.build(dashboardDTO);
|
||||
|
||||
// All timestamps should be preserved as ISO strings
|
||||
expect(result.nextRace?.scheduledAt).toBe(futureDate.toISOString());
|
||||
expect(result.feedItems[0].timestamp).toBe(feedTimestamp.toISOString());
|
||||
});
|
||||
|
||||
it('should all handle boolean flags correctly', () => {
|
||||
const dashboardDTO: DashboardOverviewDTO = {
|
||||
myUpcomingRaces: [],
|
||||
otherUpcomingRaces: [],
|
||||
upcomingRaces: [
|
||||
{
|
||||
id: 'race-1',
|
||||
track: 'Spa',
|
||||
car: 'Porsche',
|
||||
scheduledAt: new Date().toISOString(),
|
||||
status: 'scheduled',
|
||||
isMyLeague: true,
|
||||
},
|
||||
{
|
||||
id: 'race-2',
|
||||
track: 'Monza',
|
||||
car: 'Ferrari',
|
||||
scheduledAt: new Date().toISOString(),
|
||||
status: 'scheduled',
|
||||
isMyLeague: false,
|
||||
},
|
||||
],
|
||||
activeLeaguesCount: 1,
|
||||
nextRace: null,
|
||||
recentResults: [],
|
||||
leagueStandingsSummaries: [],
|
||||
feedSummary: {
|
||||
notificationCount: 0,
|
||||
items: [],
|
||||
},
|
||||
friends: [],
|
||||
};
|
||||
|
||||
const result = DashboardViewDataBuilder.build(dashboardDTO);
|
||||
|
||||
expect(result.upcomingRaces[0].isMyLeague).toBe(true);
|
||||
expect(result.upcomingRaces[1].isMyLeague).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('data integrity', () => {
|
||||
it('should maintain data consistency across transformations', () => {
|
||||
const dashboardDTO: DashboardOverviewDTO = {
|
||||
currentDriver: {
|
||||
id: 'driver-123',
|
||||
name: 'John Doe',
|
||||
country: 'USA',
|
||||
rating: 1234.56,
|
||||
globalRank: 42,
|
||||
totalRaces: 150,
|
||||
wins: 25,
|
||||
podiums: 60,
|
||||
consistency: 85,
|
||||
},
|
||||
myUpcomingRaces: [],
|
||||
otherUpcomingRaces: [],
|
||||
upcomingRaces: [],
|
||||
activeLeaguesCount: 3,
|
||||
nextRace: null,
|
||||
recentResults: [],
|
||||
leagueStandingsSummaries: [],
|
||||
feedSummary: {
|
||||
notificationCount: 5,
|
||||
items: [],
|
||||
},
|
||||
friends: [
|
||||
{ id: 'friend-1', name: 'Alice', country: 'UK' },
|
||||
{ id: 'friend-2', name: 'Bob', country: 'Germany' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = DashboardViewDataBuilder.build(dashboardDTO);
|
||||
|
||||
// Verify derived fields match their source data
|
||||
expect(result.friendCount).toBe(dashboardDTO.friends.length.toString());
|
||||
expect(result.activeLeaguesCount).toBe(dashboardDTO.activeLeaguesCount.toString());
|
||||
expect(result.hasFriends).toBe(dashboardDTO.friends.length > 0);
|
||||
expect(result.hasUpcomingRaces).toBe(dashboardDTO.upcomingRaces.length > 0);
|
||||
expect(result.hasLeagueStandings).toBe(dashboardDTO.leagueStandingsSummaries.length > 0);
|
||||
expect(result.hasFeedItems).toBe(dashboardDTO.feedSummary.items.length > 0);
|
||||
});
|
||||
|
||||
it('should handle complex real-world scenarios', () => {
|
||||
const now = new Date();
|
||||
const race1Date = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000);
|
||||
const race2Date = new Date(now.getTime() + 5 * 24 * 60 * 60 * 1000);
|
||||
const feedTimestamp = new Date(now.getTime() - 60 * 60 * 1000);
|
||||
|
||||
const dashboardDTO: DashboardOverviewDTO = {
|
||||
currentDriver: {
|
||||
id: 'driver-123',
|
||||
name: 'John Doe',
|
||||
country: 'USA',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
rating: 2456.78,
|
||||
globalRank: 15,
|
||||
totalRaces: 250,
|
||||
wins: 45,
|
||||
podiums: 120,
|
||||
consistency: 92.5,
|
||||
},
|
||||
myUpcomingRaces: [],
|
||||
otherUpcomingRaces: [],
|
||||
upcomingRaces: [
|
||||
{
|
||||
id: 'race-1',
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'Pro League',
|
||||
track: 'Spa',
|
||||
car: 'Porsche 911 GT3',
|
||||
scheduledAt: race1Date.toISOString(),
|
||||
status: 'scheduled',
|
||||
isMyLeague: true,
|
||||
},
|
||||
{
|
||||
id: 'race-2',
|
||||
track: 'Monza',
|
||||
car: 'Ferrari 488 GT3',
|
||||
scheduledAt: race2Date.toISOString(),
|
||||
status: 'scheduled',
|
||||
isMyLeague: false,
|
||||
},
|
||||
],
|
||||
activeLeaguesCount: 2,
|
||||
nextRace: {
|
||||
id: 'race-1',
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'Pro League',
|
||||
track: 'Spa',
|
||||
car: 'Porsche 911 GT3',
|
||||
scheduledAt: race1Date.toISOString(),
|
||||
status: 'scheduled',
|
||||
isMyLeague: true,
|
||||
},
|
||||
recentResults: [],
|
||||
leagueStandingsSummaries: [
|
||||
{
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'Pro League',
|
||||
position: 3,
|
||||
totalDrivers: 100,
|
||||
points: 2450,
|
||||
},
|
||||
{
|
||||
leagueId: 'league-2',
|
||||
leagueName: 'Rookie League',
|
||||
position: 1,
|
||||
totalDrivers: 50,
|
||||
points: 1800,
|
||||
},
|
||||
],
|
||||
feedSummary: {
|
||||
notificationCount: 3,
|
||||
items: [
|
||||
{
|
||||
id: 'feed-1',
|
||||
type: 'race_result',
|
||||
headline: 'Race completed',
|
||||
body: 'You finished 3rd in the Pro League race',
|
||||
timestamp: feedTimestamp.toISOString(),
|
||||
ctaLabel: 'View Results',
|
||||
ctaHref: '/races/123',
|
||||
},
|
||||
{
|
||||
id: 'feed-2',
|
||||
type: 'league_update',
|
||||
headline: 'League standings updated',
|
||||
body: 'You moved up 2 positions',
|
||||
timestamp: feedTimestamp.toISOString(),
|
||||
},
|
||||
],
|
||||
},
|
||||
friends: [
|
||||
{ id: 'friend-1', name: 'Alice', country: 'UK', avatarUrl: 'https://example.com/alice.jpg' },
|
||||
{ id: 'friend-2', name: 'Bob', country: 'Germany' },
|
||||
{ id: 'friend-3', name: 'Charlie', country: 'France', avatarUrl: 'https://example.com/charlie.jpg' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = DashboardViewDataBuilder.build(dashboardDTO);
|
||||
|
||||
// Verify all transformations
|
||||
expect(result.currentDriver.name).toBe('John Doe');
|
||||
expect(result.currentDriver.rating).toBe('2,457');
|
||||
expect(result.currentDriver.rank).toBe('15');
|
||||
expect(result.currentDriver.totalRaces).toBe('250');
|
||||
expect(result.currentDriver.wins).toBe('45');
|
||||
expect(result.currentDriver.podiums).toBe('120');
|
||||
expect(result.currentDriver.consistency).toBe('92.5%');
|
||||
|
||||
expect(result.nextRace).not.toBeNull();
|
||||
expect(result.nextRace?.id).toBe('race-1');
|
||||
expect(result.nextRace?.track).toBe('Spa');
|
||||
expect(result.nextRace?.isMyLeague).toBe(true);
|
||||
|
||||
expect(result.upcomingRaces).toHaveLength(2);
|
||||
expect(result.upcomingRaces[0].isMyLeague).toBe(true);
|
||||
expect(result.upcomingRaces[1].isMyLeague).toBe(false);
|
||||
|
||||
expect(result.leagueStandings).toHaveLength(2);
|
||||
expect(result.leagueStandings[0].position).toBe('#3');
|
||||
expect(result.leagueStandings[0].points).toBe('2450');
|
||||
expect(result.leagueStandings[1].position).toBe('#1');
|
||||
expect(result.leagueStandings[1].points).toBe('1800');
|
||||
|
||||
expect(result.feedItems).toHaveLength(2);
|
||||
expect(result.feedItems[0].type).toBe('race_result');
|
||||
expect(result.feedItems[0].ctaLabel).toBe('View Results');
|
||||
expect(result.feedItems[1].type).toBe('league_update');
|
||||
expect(result.feedItems[1].ctaLabel).toBeUndefined();
|
||||
|
||||
expect(result.friends).toHaveLength(3);
|
||||
expect(result.friends[0].avatarUrl).toBe('https://example.com/alice.jpg');
|
||||
expect(result.friends[1].avatarUrl).toBe('');
|
||||
expect(result.friends[2].avatarUrl).toBe('https://example.com/charlie.jpg');
|
||||
|
||||
expect(result.activeLeaguesCount).toBe('2');
|
||||
expect(result.friendCount).toBe('3');
|
||||
expect(result.hasUpcomingRaces).toBe(true);
|
||||
expect(result.hasLeagueStandings).toBe(true);
|
||||
expect(result.hasFeedItems).toBe(true);
|
||||
expect(result.hasFriends).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
export class AchievementDisplay {
|
||||
export class AchievementFormatter {
|
||||
static getRarityVariant(rarity: string) {
|
||||
switch (rarity.toLowerCase()) {
|
||||
case 'common':
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic mapping of engagement rates to activity level labels.
|
||||
*/
|
||||
|
||||
export class ActivityLevelDisplay {
|
||||
export class ActivityLevelFormatter {
|
||||
/**
|
||||
* Maps engagement rate to activity level label.
|
||||
*/
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic mapping of avatar-related data to display formats.
|
||||
*/
|
||||
|
||||
export class AvatarDisplay {
|
||||
export class AvatarFormatter {
|
||||
/**
|
||||
* Converts binary buffer to base64 string for display.
|
||||
*/
|
||||
@@ -1,18 +1,18 @@
|
||||
export class CountryFlagDisplay {
|
||||
export class CountryFlagFormatter {
|
||||
private constructor(private readonly value: string) {}
|
||||
|
||||
static fromCountryCode(countryCode: string | null | undefined): CountryFlagDisplay {
|
||||
static fromCountryCode(countryCode: string | null | undefined): CountryFlagFormatter {
|
||||
if (!countryCode) {
|
||||
return new CountryFlagDisplay('🏁');
|
||||
return new CountryFlagFormatter('🏁');
|
||||
}
|
||||
|
||||
const code = countryCode.toUpperCase();
|
||||
if (code.length !== 2) {
|
||||
return new CountryFlagDisplay('🏁');
|
||||
return new CountryFlagFormatter('🏁');
|
||||
}
|
||||
|
||||
const codePoints = [...code].map((char) => 127397 + char.charCodeAt(0));
|
||||
return new CountryFlagDisplay(String.fromCodePoint(...codePoints));
|
||||
return new CountryFlagFormatter(String.fromCodePoint(...codePoints));
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
@@ -5,7 +5,7 @@
|
||||
* Avoids Intl and toLocaleString to prevent SSR/hydration mismatches.
|
||||
*/
|
||||
|
||||
export class CurrencyDisplay {
|
||||
export class CurrencyFormatter {
|
||||
/**
|
||||
* Formats an amount as currency (e.g., "$10.00" or "€1.000,00").
|
||||
* Default currency is USD.
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { DashboardConsistencyDisplay } from './DashboardConsistencyDisplay';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { DashboardConsistencyDisplay } from './DashboardConsistencyFormatter';
|
||||
|
||||
describe('DashboardConsistencyDisplay', () => {
|
||||
describe('happy paths', () => {
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic consistency formatting for dashboard display.
|
||||
*/
|
||||
|
||||
export class DashboardConsistencyDisplay {
|
||||
export class DashboardConsistencyFormatter {
|
||||
static format(consistency: number): string {
|
||||
return `${consistency}%`;
|
||||
}
|
||||
38
apps/website/lib/formatters/DashboardCountFormatter.test.ts
Normal file
38
apps/website/lib/formatters/DashboardCountFormatter.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { DashboardCountFormatter } from './DashboardCountFormatter';
|
||||
|
||||
describe('DashboardCountDisplay', () => {
|
||||
describe('happy paths', () => {
|
||||
it('should format positive numbers correctly', () => {
|
||||
expect(DashboardCountFormatter.format(0)).toBe('0');
|
||||
expect(DashboardCountFormatter.format(1)).toBe('1');
|
||||
expect(DashboardCountFormatter.format(100)).toBe('100');
|
||||
expect(DashboardCountFormatter.format(1000)).toBe('1000');
|
||||
});
|
||||
|
||||
it('should handle null values', () => {
|
||||
expect(DashboardCountFormatter.format(null)).toBe('0');
|
||||
});
|
||||
|
||||
it('should handle undefined values', () => {
|
||||
expect(DashboardCountFormatter.format(undefined)).toBe('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle negative numbers', () => {
|
||||
expect(DashboardCountFormatter.format(-1)).toBe('-1');
|
||||
expect(DashboardCountFormatter.format(-100)).toBe('-100');
|
||||
});
|
||||
|
||||
it('should handle large numbers', () => {
|
||||
expect(DashboardCountFormatter.format(999999)).toBe('999999');
|
||||
expect(DashboardCountFormatter.format(1000000)).toBe('1000000');
|
||||
});
|
||||
|
||||
it('should handle decimal numbers', () => {
|
||||
expect(DashboardCountFormatter.format(1.5)).toBe('1.5');
|
||||
expect(DashboardCountFormatter.format(100.99)).toBe('100.99');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic count formatting for dashboard display.
|
||||
*/
|
||||
|
||||
export class DashboardCountDisplay {
|
||||
export class DashboardCountFormatter {
|
||||
static format(count: number | null | undefined): string {
|
||||
if (count === null || count === undefined) {
|
||||
return '0';
|
||||
@@ -14,7 +14,7 @@ export interface DashboardDateDisplayData {
|
||||
/**
|
||||
* Format date for display (deterministic, no Intl)
|
||||
*/
|
||||
export class DashboardDateDisplay {
|
||||
export class DashboardDateFormatter {
|
||||
static format(date: Date): DashboardDateDisplayData {
|
||||
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { DashboardLeaguePositionDisplay } from './DashboardLeaguePositionDisplay';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { DashboardLeaguePositionDisplay } from './DashboardLeaguePositionFormatter';
|
||||
|
||||
describe('DashboardLeaguePositionDisplay', () => {
|
||||
describe('happy paths', () => {
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic league position formatting for dashboard display.
|
||||
*/
|
||||
|
||||
export class DashboardLeaguePositionDisplay {
|
||||
export class DashboardLeaguePositionFormatter {
|
||||
static format(position: number | null | undefined): string {
|
||||
if (position === null || position === undefined) {
|
||||
return '-';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { DashboardRankDisplay } from './DashboardRankDisplay';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { DashboardRankDisplay } from './DashboardRankFormatter';
|
||||
|
||||
describe('DashboardRankDisplay', () => {
|
||||
describe('happy paths', () => {
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic rank formatting for dashboard display.
|
||||
*/
|
||||
|
||||
export class DashboardRankDisplay {
|
||||
export class DashboardRankFormatter {
|
||||
static format(rank: number): string {
|
||||
return rank.toString();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export class DateDisplay {
|
||||
export class DateFormatter {
|
||||
/**
|
||||
* Formats a date as "Jan 18, 2026" using UTC.
|
||||
*/
|
||||
@@ -5,7 +5,7 @@
|
||||
* to UI labels and variants.
|
||||
*/
|
||||
|
||||
export class DriverRegistrationStatusDisplay {
|
||||
export class DriverRegistrationStatusFormatter {
|
||||
static statusMessage(isRegistered: boolean): string {
|
||||
return isRegistered ? "Registered for this race" : "Not registered";
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic formatting for time durations.
|
||||
*/
|
||||
|
||||
export class DurationDisplay {
|
||||
export class DurationFormatter {
|
||||
/**
|
||||
* Formats milliseconds as "123.45ms".
|
||||
*/
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic formatting for race finish positions.
|
||||
*/
|
||||
|
||||
export class FinishDisplay {
|
||||
export class FinishFormatter {
|
||||
/**
|
||||
* Formats a finish position as "P1", "P2", etc.
|
||||
*/
|
||||
@@ -5,7 +5,7 @@
|
||||
* This display object isolates UI-specific formatting from business logic.
|
||||
*/
|
||||
|
||||
export class HealthAlertDisplay {
|
||||
export class HealthAlertFormatter {
|
||||
static formatSeverity(type: 'critical' | 'warning' | 'info'): string {
|
||||
const severities: Record<string, string> = {
|
||||
critical: 'Critical',
|
||||
@@ -5,7 +5,7 @@
|
||||
* This display object isolates UI-specific formatting from business logic.
|
||||
*/
|
||||
|
||||
export class HealthComponentDisplay {
|
||||
export class HealthComponentFormatter {
|
||||
static formatStatusLabel(status: 'ok' | 'degraded' | 'error' | 'unknown'): string {
|
||||
const labels: Record<string, string> = {
|
||||
ok: 'Healthy',
|
||||
@@ -5,7 +5,7 @@
|
||||
* This display object isolates UI-specific formatting from business logic.
|
||||
*/
|
||||
|
||||
export class HealthMetricDisplay {
|
||||
export class HealthMetricFormatter {
|
||||
static formatUptime(uptime?: number): string {
|
||||
if (uptime === undefined || uptime === null) return 'N/A';
|
||||
if (uptime < 0) return 'N/A';
|
||||
@@ -5,7 +5,7 @@
|
||||
* This display object isolates UI-specific formatting from business logic.
|
||||
*/
|
||||
|
||||
export class HealthStatusDisplay {
|
||||
export class HealthStatusFormatter {
|
||||
static formatStatusLabel(status: 'ok' | 'degraded' | 'error' | 'unknown'): string {
|
||||
const labels: Record<string, string> = {
|
||||
ok: 'Healthy',
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic mapping of league creation status to display messages.
|
||||
*/
|
||||
|
||||
export class LeagueCreationStatusDisplay {
|
||||
export class LeagueCreationStatusFormatter {
|
||||
/**
|
||||
* Maps league creation success status to display message.
|
||||
*/
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic display logic for leagues.
|
||||
*/
|
||||
|
||||
export class LeagueDisplay {
|
||||
export class LeagueFormatter {
|
||||
/**
|
||||
* Formats a league count with pluralization.
|
||||
* Example: 1 -> "1 league", 2 -> "2 leagues"
|
||||
@@ -25,7 +25,7 @@ export const leagueRoleDisplay: Record<LeagueRole, LeagueRoleDisplayData> = {
|
||||
} as const;
|
||||
|
||||
// For backward compatibility, also export the class with static method
|
||||
export class LeagueRoleDisplay {
|
||||
export class LeagueRoleFormatter {
|
||||
static getLeagueRoleDisplay(role: LeagueRole) {
|
||||
return leagueRoleDisplay[role];
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export interface LeagueTierDisplayData {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export class LeagueTierDisplay {
|
||||
export class LeagueTierFormatter {
|
||||
private static readonly CONFIG: Record<string, LeagueTierDisplayData> = {
|
||||
premium: {
|
||||
color: 'text-yellow-400',
|
||||
@@ -1,3 +1,4 @@
|
||||
// TODO this file has no clear meaning
|
||||
export class LeagueWizardValidationMessages {
|
||||
static readonly LEAGUE_NAME_REQUIRED = 'League name is required';
|
||||
static readonly LEAGUE_NAME_TOO_SHORT = 'League name must be at least 3 characters';
|
||||
@@ -1,4 +1,4 @@
|
||||
export class MedalDisplay {
|
||||
export class MedalFormatter {
|
||||
static getVariant(position: number): 'warning' | 'low' | 'high' {
|
||||
switch (position) {
|
||||
case 1: return 'warning';
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic display logic for members.
|
||||
*/
|
||||
|
||||
export class MemberDisplay {
|
||||
export class MemberFormatter {
|
||||
/**
|
||||
* Formats a member count with pluralization.
|
||||
* Example: 1 -> "1 member", 2 -> "2 members"
|
||||
@@ -1,4 +1,4 @@
|
||||
export class MembershipFeeTypeDisplay {
|
||||
export class MembershipFeeTypeFormatter {
|
||||
static format(type: string): string {
|
||||
switch (type) {
|
||||
case 'season': return 'Per Season';
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic formatting for memory usage.
|
||||
*/
|
||||
|
||||
export class MemoryDisplay {
|
||||
export class MemoryFormatter {
|
||||
/**
|
||||
* Formats bytes as "123.4MB".
|
||||
*/
|
||||
@@ -5,7 +5,7 @@
|
||||
* Avoids Intl and toLocaleString to prevent SSR/hydration mismatches.
|
||||
*/
|
||||
|
||||
export class NumberDisplay {
|
||||
export class NumberFormatter {
|
||||
/**
|
||||
* Formats a number with thousands separators (commas).
|
||||
* Example: 1234567 -> "1,234,567"
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic mapping of onboarding status to display labels and variants.
|
||||
*/
|
||||
|
||||
export class OnboardingStatusDisplay {
|
||||
export class OnboardingStatusFormatter {
|
||||
/**
|
||||
* Maps onboarding success status to display label.
|
||||
*/
|
||||
@@ -1,4 +1,4 @@
|
||||
export class PayerTypeDisplay {
|
||||
export class PayerTypeFormatter {
|
||||
static format(type: string): string {
|
||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export class PaymentTypeDisplay {
|
||||
export class PaymentTypeFormatter {
|
||||
static format(type: string): string {
|
||||
return type.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic formatting for percentages.
|
||||
*/
|
||||
|
||||
export class PercentDisplay {
|
||||
export class PercentFormatter {
|
||||
/**
|
||||
* Formats a decimal value as a percentage string.
|
||||
* Example: 0.1234 -> "12.3%"
|
||||
@@ -1,4 +1,4 @@
|
||||
export class PrizeTypeDisplay {
|
||||
export class PrizeTypeFormatter {
|
||||
static format(type: string): string {
|
||||
switch (type) {
|
||||
case 'cash': return 'Cash Prize';
|
||||
@@ -30,7 +30,7 @@ export interface TeamRoleDisplayData {
|
||||
badgeClasses: string;
|
||||
}
|
||||
|
||||
export class ProfileDisplay {
|
||||
export class ProfileFormatter {
|
||||
private static readonly countryFlagDisplay: Record<string, CountryFlagDisplayData> = {
|
||||
US: { flag: '🇺🇸', label: 'United States' },
|
||||
GB: { flag: '🇬🇧', label: 'United Kingdom' },
|
||||
@@ -47,7 +47,7 @@ export class ProfileDisplay {
|
||||
|
||||
static getCountryFlag(countryCode: string): CountryFlagDisplayData {
|
||||
const code = countryCode.toUpperCase();
|
||||
return ProfileDisplay.countryFlagDisplay[code] || ProfileDisplay.countryFlagDisplay.DEFAULT;
|
||||
return ProfileFormatter.countryFlagDisplay[code] || ProfileFormatter.countryFlagDisplay.DEFAULT;
|
||||
}
|
||||
|
||||
private static readonly achievementRarityDisplay: Record<string, AchievementRarityDisplayData> = {
|
||||
@@ -74,7 +74,7 @@ export class ProfileDisplay {
|
||||
};
|
||||
|
||||
static getAchievementRarity(rarity: string): AchievementRarityDisplayData {
|
||||
return ProfileDisplay.achievementRarityDisplay[rarity] || ProfileDisplay.achievementRarityDisplay.common;
|
||||
return ProfileFormatter.achievementRarityDisplay[rarity] || ProfileFormatter.achievementRarityDisplay.common;
|
||||
}
|
||||
|
||||
private static readonly achievementIconDisplay: Record<string, AchievementIconDisplayData> = {
|
||||
@@ -87,7 +87,7 @@ export class ProfileDisplay {
|
||||
};
|
||||
|
||||
static getAchievementIcon(icon: string): AchievementIconDisplayData {
|
||||
return ProfileDisplay.achievementIconDisplay[icon] || ProfileDisplay.achievementIconDisplay.trophy;
|
||||
return ProfileFormatter.achievementIconDisplay[icon] || ProfileFormatter.achievementIconDisplay.trophy;
|
||||
}
|
||||
|
||||
private static readonly socialPlatformDisplay: Record<string, SocialPlatformDisplayData> = {
|
||||
@@ -110,7 +110,7 @@ export class ProfileDisplay {
|
||||
};
|
||||
|
||||
static getSocialPlatform(platform: string): SocialPlatformDisplayData {
|
||||
return ProfileDisplay.socialPlatformDisplay[platform] || ProfileDisplay.socialPlatformDisplay.discord;
|
||||
return ProfileFormatter.socialPlatformDisplay[platform] || ProfileFormatter.socialPlatformDisplay.discord;
|
||||
}
|
||||
|
||||
static formatMonthYear(dateString: string): string {
|
||||
@@ -184,6 +184,6 @@ export class ProfileDisplay {
|
||||
};
|
||||
|
||||
static getTeamRole(role: string): TeamRoleDisplayData {
|
||||
return ProfileDisplay.teamRoleDisplay[role] || ProfileDisplay.teamRoleDisplay.member;
|
||||
return ProfileFormatter.teamRoleDisplay[role] || ProfileFormatter.teamRoleDisplay.member;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
export type RaceStatusVariant = 'info' | 'success' | 'neutral' | 'warning' | 'primary' | 'default';
|
||||
|
||||
export class RaceStatusDisplay {
|
||||
export class RaceStatusFormatter {
|
||||
private static readonly CONFIG: Record<string, { variant: RaceStatusVariant; label: string; icon: string }> = {
|
||||
scheduled: {
|
||||
variant: 'primary',
|
||||
@@ -1,12 +1,12 @@
|
||||
import { NumberDisplay } from './NumberDisplay';
|
||||
import { NumberFormatter } from './NumberFormatter';
|
||||
|
||||
export class RatingDisplay {
|
||||
export class RatingFormatter {
|
||||
/**
|
||||
* Formats a rating as a rounded number with thousands separators.
|
||||
* Example: 1234.56 -> "1,235"
|
||||
*/
|
||||
static format(rating: number | null | undefined): string {
|
||||
if (rating === null || rating === undefined) return '—';
|
||||
return NumberDisplay.format(Math.round(rating));
|
||||
return NumberFormatter.format(Math.round(rating));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export class RatingTrendDisplay {
|
||||
export class RatingTrendFormatter {
|
||||
static getTrend(currentRating: number, previousRating: number | undefined): 'up' | 'down' | 'same' {
|
||||
if (!previousRating) return 'same';
|
||||
if (currentRating > previousRating) return 'up';
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic relative time formatting.
|
||||
*/
|
||||
|
||||
export class RelativeTimeDisplay {
|
||||
export class RelativeTimeFormatter {
|
||||
/**
|
||||
* Formats a date relative to "now".
|
||||
* "now" must be passed as an argument for determinism.
|
||||
@@ -10,7 +10,7 @@ export interface SeasonStatusDisplayData {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export class SeasonStatusDisplay {
|
||||
export class SeasonStatusFormatter {
|
||||
private static readonly CONFIG: Record<string, SeasonStatusDisplayData> = {
|
||||
active: {
|
||||
color: 'text-performance-green',
|
||||
@@ -1,4 +1,4 @@
|
||||
export class SkillLevelDisplay {
|
||||
export class SkillLevelFormatter {
|
||||
static getLabel(skillLevel: string): string {
|
||||
const levels: Record<string, string> = {
|
||||
pro: 'Pro',
|
||||
@@ -1,4 +1,4 @@
|
||||
export class SkillLevelIconDisplay {
|
||||
export class SkillLevelIconFormatter {
|
||||
static getIcon(skillLevel: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
beginner: '🥉',
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic mapping of status codes to human-readable labels.
|
||||
*/
|
||||
|
||||
export class StatusDisplay {
|
||||
export class StatusFormatter {
|
||||
/**
|
||||
* Maps transaction status to label.
|
||||
*/
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic mapping of team creation status to display messages.
|
||||
*/
|
||||
|
||||
export class TeamCreationStatusDisplay {
|
||||
export class TeamCreationStatusFormatter {
|
||||
/**
|
||||
* Maps team creation success status to display message.
|
||||
*/
|
||||
@@ -1,4 +1,4 @@
|
||||
export class TimeDisplay {
|
||||
export class TimeFormatter {
|
||||
static timeAgo(timestamp: Date | string): string {
|
||||
const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
|
||||
const diffMs = Date.now() - date.getTime();
|
||||
@@ -1,4 +1,4 @@
|
||||
export class TransactionTypeDisplay {
|
||||
export class TransactionTypeFormatter {
|
||||
static format(type: string): string {
|
||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic mapping of user role codes to display labels.
|
||||
*/
|
||||
|
||||
export class UserRoleDisplay {
|
||||
export class UserRoleFormatter {
|
||||
/**
|
||||
* Maps user role to display label.
|
||||
*/
|
||||
@@ -4,7 +4,7 @@
|
||||
* Deterministic mapping of user status codes to display labels and variants.
|
||||
*/
|
||||
|
||||
export class UserStatusDisplay {
|
||||
export class UserStatusFormatter {
|
||||
/**
|
||||
* Maps user status to display label.
|
||||
*/
|
||||
@@ -1,4 +1,4 @@
|
||||
export class WinRateDisplay {
|
||||
export class WinRateFormatter {
|
||||
static calculate(racesCompleted: number, wins: number): string {
|
||||
if (racesCompleted === 0) return '0.0';
|
||||
const rate = (wins / racesCompleted) * 100;
|
||||
@@ -1,24 +1,24 @@
|
||||
import { FeatureFlagService } from '@/lib/feature/FeatureFlagService';
|
||||
|
||||
// API Clients
|
||||
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
|
||||
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
|
||||
|
||||
// Services
|
||||
import { SessionService } from '@/lib/services/auth/SessionService';
|
||||
|
||||
// Infrastructure
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
// DTO types
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import type { Service } from '@/lib/contracts/services/Service';
|
||||
import type { HomeDataDTO } from '@/lib/types/dtos/HomeDataDTO';
|
||||
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { DateFormatter } from '@/lib/formatters/DateFormatter';
|
||||
|
||||
/**
|
||||
* HomeService
|
||||
@@ -56,7 +56,7 @@ export class HomeService implements Service {
|
||||
id: r.id,
|
||||
track: r.track,
|
||||
car: r.car,
|
||||
formattedDate: DateDisplay.formatShort(r.scheduledAt),
|
||||
formattedDate: DateFormatter.formatShort(r.scheduledAt),
|
||||
})),
|
||||
topLeagues: leaguesDto.leagues.slice(0, 4).map(l => ({
|
||||
id: l.id,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DateFormatter } from "@/lib/formatters/DateFormatter";
|
||||
import { UserRoleFormatter } from "@/lib/formatters/UserRoleFormatter";
|
||||
import { UserStatusFormatter } from "@/lib/formatters/UserStatusFormatter";
|
||||
import type { AdminUserViewData } from '@/lib/view-data/AdminUserViewData';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { UserStatusDisplay } from "@/lib/display-objects/UserStatusDisplay";
|
||||
import { UserRoleDisplay } from "@/lib/display-objects/UserRoleDisplay";
|
||||
import { DateDisplay } from "@/lib/display-objects/DateDisplay";
|
||||
|
||||
/**
|
||||
* AdminUserViewModel
|
||||
@@ -31,28 +31,28 @@ export class AdminUserViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Role badges using Display Object */
|
||||
get roleBadges(): string[] {
|
||||
return this.roles.map(role => UserRoleDisplay.roleLabel(role));
|
||||
return this.roles.map(role => UserRoleFormatter.roleLabel(role));
|
||||
}
|
||||
|
||||
/** UI-specific: Status badge label using Display Object */
|
||||
get statusBadgeLabel(): string {
|
||||
return UserStatusDisplay.statusLabel(this.status);
|
||||
return UserStatusFormatter.statusLabel(this.status);
|
||||
}
|
||||
|
||||
/** UI-specific: Status badge variant using Display Object */
|
||||
get statusBadgeVariant(): string {
|
||||
return UserStatusDisplay.statusVariant(this.status);
|
||||
return UserStatusFormatter.statusVariant(this.status);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted last login date */
|
||||
get lastLoginFormatted(): string {
|
||||
return this.lastLoginAt
|
||||
? DateDisplay.formatShort(this.lastLoginAt)
|
||||
? DateFormatter.formatShort(this.lastLoginAt)
|
||||
: 'Never';
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted creation date */
|
||||
get createdAtFormatted(): string {
|
||||
return DateDisplay.formatShort(this.createdAt);
|
||||
return DateFormatter.formatShort(this.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*
|
||||
* Accepts AnalyticsMetricsViewData as input and produces UI-ready data.
|
||||
*/
|
||||
import { AnalyticsMetricsViewData } from "../view-data/AnalyticsMetricsViewData";
|
||||
import { DurationFormatter } from "@/lib/formatters/DurationFormatter";
|
||||
import { NumberFormatter } from "@/lib/formatters/NumberFormatter";
|
||||
import { PercentFormatter } from "@/lib/formatters/PercentFormatter";
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { NumberDisplay } from "@/lib/display-objects/NumberDisplay";
|
||||
import { DurationDisplay } from "@/lib/display-objects/DurationDisplay";
|
||||
import { PercentDisplay } from "@/lib/display-objects/PercentDisplay";
|
||||
import { AnalyticsMetricsViewData } from "../view-data/AnalyticsMetricsViewData";
|
||||
|
||||
export class AnalyticsMetricsViewModel extends ViewModel {
|
||||
private readonly data: AnalyticsMetricsViewData;
|
||||
@@ -25,21 +25,21 @@ export class AnalyticsMetricsViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted page views */
|
||||
get formattedPageViews(): string {
|
||||
return NumberDisplay.format(this.pageViews);
|
||||
return NumberFormatter.format(this.pageViews);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted unique visitors */
|
||||
get formattedUniqueVisitors(): string {
|
||||
return NumberDisplay.format(this.uniqueVisitors);
|
||||
return NumberFormatter.format(this.uniqueVisitors);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted session duration */
|
||||
get formattedSessionDuration(): string {
|
||||
return DurationDisplay.formatSeconds(this.averageSessionDuration);
|
||||
return DurationFormatter.formatSeconds(this.averageSessionDuration);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted bounce rate */
|
||||
get formattedBounceRate(): string {
|
||||
return PercentDisplay.format(this.bounceRate);
|
||||
return PercentFormatter.format(this.bounceRate);
|
||||
}
|
||||
}
|
||||
@@ -6,19 +6,17 @@
|
||||
* Accepts AvailableLeaguesViewData as input and produces UI-ready data.
|
||||
*/
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import { LeagueTierFormatter } from "../formatters/LeagueTierFormatter";
|
||||
import { NumberFormatter } from "../formatters/NumberFormatter";
|
||||
import { SeasonStatusFormatter } from "../formatters/SeasonStatusFormatter";
|
||||
import { AvailableLeaguesViewData, AvailableLeagueViewData } from "../view-data/AvailableLeaguesViewData";
|
||||
import { NumberDisplay } from "../display-objects/NumberDisplay";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { LeagueTierDisplay } from "../display-objects/LeagueTierDisplay";
|
||||
import { SeasonStatusDisplay } from "../display-objects/SeasonStatusDisplay";
|
||||
|
||||
export class AvailableLeaguesViewModel extends ViewModel {
|
||||
private readonly data: AvailableLeaguesViewData;
|
||||
readonly leagues: AvailableLeagueViewModel[];
|
||||
|
||||
constructor(data: AvailableLeaguesViewData) {
|
||||
super();
|
||||
this.data = data;
|
||||
this.leagues = data.leagues.map(league => new AvailableLeagueViewModel(league));
|
||||
}
|
||||
}
|
||||
@@ -46,7 +44,7 @@ export class AvailableLeagueViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted average views */
|
||||
get formattedAvgViews(): string {
|
||||
return NumberDisplay.formatCompact(this.avgViewsPerRace);
|
||||
return NumberFormatter.formatCompact(this.avgViewsPerRace);
|
||||
}
|
||||
|
||||
/** UI-specific: CPM calculation */
|
||||
@@ -56,7 +54,7 @@ export class AvailableLeagueViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted CPM */
|
||||
get formattedCpm(): string {
|
||||
return CurrencyDisplay.formatCompact(this.cpm);
|
||||
return CurrencyFormatter.formatCompact(this.cpm);
|
||||
}
|
||||
|
||||
/** UI-specific: Check if any sponsor slots are available */
|
||||
@@ -66,12 +64,12 @@ export class AvailableLeagueViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Tier configuration for badge styling */
|
||||
get tierConfig() {
|
||||
return LeagueTierDisplay.getDisplay(this.tier);
|
||||
return LeagueTierFormatter.getDisplay(this.tier);
|
||||
}
|
||||
|
||||
/** UI-specific: Status configuration for season state */
|
||||
get statusConfig() {
|
||||
return SeasonStatusDisplay.getDisplay(this.seasonStatus);
|
||||
return SeasonStatusFormatter.getDisplay(this.seasonStatus);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { AvatarDisplay } from "../display-objects/AvatarDisplay";
|
||||
import { AvatarViewData } from "@/lib/view-data/AvatarViewData";
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { AvatarFormatter } from "../formatters/AvatarFormatter";
|
||||
|
||||
/**
|
||||
* Avatar View Model
|
||||
@@ -23,11 +23,11 @@ export class AvatarViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Derive content type label using Display Object */
|
||||
get contentTypeLabel(): string {
|
||||
return AvatarDisplay.formatContentType(this.data.contentType);
|
||||
return AvatarFormatter.formatContentType(this.data.contentType);
|
||||
}
|
||||
|
||||
/** UI-specific: Derive validity check using Display Object */
|
||||
get hasValidData(): boolean {
|
||||
return AvatarDisplay.hasValidData(this.data.buffer, this.data.contentType);
|
||||
return AvatarFormatter.hasValidData(this.data.buffer, this.data.contentType);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { OnboardingStatusDisplay } from '../display-objects/OnboardingStatusDisplay';
|
||||
import { OnboardingStatusFormatter } from '../formatters/OnboardingStatusFormatter';
|
||||
import type { CompleteOnboardingViewData } from '../view-data/CompleteOnboardingViewData';
|
||||
|
||||
/**
|
||||
@@ -22,22 +22,22 @@ export class CompleteOnboardingViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Status label using Display Object */
|
||||
get statusLabel(): string {
|
||||
return OnboardingStatusDisplay.statusLabel(this.success);
|
||||
return OnboardingStatusFormatter.statusLabel(this.success);
|
||||
}
|
||||
|
||||
/** UI-specific: Status variant using Display Object */
|
||||
get statusVariant(): string {
|
||||
return OnboardingStatusDisplay.statusVariant(this.success);
|
||||
return OnboardingStatusFormatter.statusVariant(this.success);
|
||||
}
|
||||
|
||||
/** UI-specific: Status icon using Display Object */
|
||||
get statusIcon(): string {
|
||||
return OnboardingStatusDisplay.statusIcon(this.success);
|
||||
return OnboardingStatusFormatter.statusIcon(this.success);
|
||||
}
|
||||
|
||||
/** UI-specific: Status message using Display Object */
|
||||
get statusMessage(): string {
|
||||
return OnboardingStatusDisplay.statusMessage(this.success, this.errorMessage);
|
||||
return OnboardingStatusFormatter.statusMessage(this.success, this.errorMessage);
|
||||
}
|
||||
|
||||
/** UI-specific: Whether onboarding was successful */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CreateLeagueViewData } from '../view-data/CreateLeagueViewData';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { LeagueCreationStatusDisplay } from '../display-objects/LeagueCreationStatusDisplay';
|
||||
import { LeagueCreationStatusFormatter } from '../formatters/LeagueCreationStatusFormatter';
|
||||
import type { CreateLeagueViewData } from '../view-data/CreateLeagueViewData';
|
||||
|
||||
/**
|
||||
* View Model for Create League Result
|
||||
@@ -21,7 +21,7 @@ export class CreateLeagueViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Success message using Display Object */
|
||||
get successMessage(): string {
|
||||
return LeagueCreationStatusDisplay.statusMessage(this.success);
|
||||
return LeagueCreationStatusFormatter.statusMessage(this.success);
|
||||
}
|
||||
|
||||
/** UI-specific: Whether league creation was successful */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CreateTeamViewData } from '../view-data/CreateTeamViewData';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { TeamCreationStatusDisplay } from '../display-objects/TeamCreationStatusDisplay';
|
||||
import { TeamCreationStatusFormatter } from '../formatters/TeamCreationStatusFormatter';
|
||||
import type { CreateTeamViewData } from '../view-data/CreateTeamViewData';
|
||||
|
||||
/**
|
||||
* View Model for Create Team Result
|
||||
@@ -21,7 +21,7 @@ export class CreateTeamViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Success message using Display Object */
|
||||
get successMessage(): string {
|
||||
return TeamCreationStatusDisplay.statusMessage(this.success);
|
||||
return TeamCreationStatusFormatter.statusMessage(this.success);
|
||||
}
|
||||
|
||||
/** UI-specific: Whether team creation was successful */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { ActivityLevelDisplay } from "@/lib/display-objects/ActivityLevelDisplay";
|
||||
import { ActivityLevelFormatter } from "@/lib/formatters/ActivityLevelFormatter";
|
||||
import type { DashboardStatsViewData } from '@/lib/view-data/DashboardStatsViewData';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
|
||||
/**
|
||||
* DashboardStatsViewModel
|
||||
@@ -68,7 +68,7 @@ export class DashboardStatsViewModel extends ViewModel {
|
||||
|
||||
// Derive activity level using Display Object
|
||||
const engagementRate = this.totalUsers > 0 ? (this.recentLogins / this.totalUsers) * 100 : 0;
|
||||
this.activityLevelLabel = ActivityLevelDisplay.levelLabel(engagementRate);
|
||||
this.activityLevelValue = ActivityLevelDisplay.levelValue(engagementRate);
|
||||
this.activityLevelLabel = ActivityLevelFormatter.levelLabel(engagementRate);
|
||||
this.activityLevelValue = ActivityLevelFormatter.levelValue(engagementRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { RatingTrendFormatter } from "../formatters/RatingTrendFormatter";
|
||||
import { SkillLevelFormatter } from "../formatters/SkillLevelFormatter";
|
||||
import { SkillLevelIconFormatter } from "../formatters/SkillLevelIconFormatter";
|
||||
import { WinRateFormatter } from "../formatters/WinRateFormatter";
|
||||
import type { LeaderboardDriverItem } from '../view-data/LeaderboardDriverItem';
|
||||
import { SkillLevelDisplay } from "../display-objects/SkillLevelDisplay";
|
||||
import { SkillLevelIconDisplay } from "../display-objects/SkillLevelIconDisplay";
|
||||
import { WinRateDisplay } from "../display-objects/WinRateDisplay";
|
||||
import { RatingTrendDisplay } from "../display-objects/RatingTrendDisplay";
|
||||
|
||||
export class DriverLeaderboardItemViewModel extends ViewModel {
|
||||
private readonly data: LeaderboardDriverItem;
|
||||
@@ -29,12 +29,12 @@ export class DriverLeaderboardItemViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Skill level color */
|
||||
get skillLevelColor(): string {
|
||||
return SkillLevelDisplay.getColor(this.skillLevel);
|
||||
return SkillLevelFormatter.getColor(this.skillLevel);
|
||||
}
|
||||
|
||||
/** UI-specific: Skill level icon */
|
||||
get skillLevelIcon(): string {
|
||||
return SkillLevelIconDisplay.getIcon(this.skillLevel);
|
||||
return SkillLevelIconFormatter.getIcon(this.skillLevel);
|
||||
}
|
||||
|
||||
/** UI-specific: Win rate */
|
||||
@@ -44,17 +44,17 @@ export class DriverLeaderboardItemViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted win rate */
|
||||
get winRateFormatted(): string {
|
||||
return WinRateDisplay.format(this.winRate);
|
||||
return WinRateFormatter.format(this.winRate);
|
||||
}
|
||||
|
||||
/** UI-specific: Rating trend */
|
||||
get ratingTrend(): 'up' | 'down' | 'same' {
|
||||
return RatingTrendDisplay.getTrend(this.rating, this.previousRating);
|
||||
return RatingTrendFormatter.getTrend(this.rating, this.previousRating);
|
||||
}
|
||||
|
||||
/** UI-specific: Rating change indicator */
|
||||
get ratingChangeIndicator(): string {
|
||||
return RatingTrendDisplay.getChangeIndicator(this.rating, this.previousRating);
|
||||
return RatingTrendFormatter.getChangeIndicator(this.rating, this.previousRating);
|
||||
}
|
||||
|
||||
/** UI-specific: Position badge */
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { DashboardConsistencyFormatter } from "../formatters/DashboardConsistencyFormatter";
|
||||
import { NumberFormatter } from "../formatters/NumberFormatter";
|
||||
import { RatingFormatter } from "../formatters/RatingFormatter";
|
||||
import { ProfileViewData } from "../view-data/ProfileViewData";
|
||||
import { RatingDisplay } from "../display-objects/RatingDisplay";
|
||||
import { DashboardConsistencyDisplay } from "../display-objects/DashboardConsistencyDisplay";
|
||||
import { NumberDisplay } from "../display-objects/NumberDisplay";
|
||||
|
||||
/**
|
||||
* Driver Profile Driver Summary View Model
|
||||
@@ -44,7 +44,7 @@ export class DriverProfileDriverSummaryViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get ratingLabel(): string {
|
||||
return RatingDisplay.format(this.rating);
|
||||
return RatingFormatter.format(this.rating);
|
||||
}
|
||||
|
||||
get globalRank(): number | null {
|
||||
@@ -52,7 +52,7 @@ export class DriverProfileDriverSummaryViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get globalRankLabel(): string {
|
||||
return this.globalRank ? NumberDisplay.format(this.globalRank) : '—';
|
||||
return this.globalRank ? NumberFormatter.format(this.globalRank) : '—';
|
||||
}
|
||||
|
||||
get consistency(): number | null {
|
||||
@@ -60,7 +60,7 @@ export class DriverProfileDriverSummaryViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get consistencyLabel(): string {
|
||||
return this.consistency ? DashboardConsistencyDisplay.format(this.consistency) : '—';
|
||||
return this.consistency ? DashboardConsistencyFormatter.format(this.consistency) : '—';
|
||||
}
|
||||
|
||||
get bio(): string | null {
|
||||
@@ -72,6 +72,6 @@ export class DriverProfileDriverSummaryViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get totalDriversLabel(): string {
|
||||
return this.totalDrivers ? NumberDisplay.format(this.totalDrivers) : '—';
|
||||
return this.totalDrivers ? NumberFormatter.format(this.totalDrivers) : '—';
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { DriverRegistrationStatusFormatter } from "../formatters/DriverRegistrationStatusFormatter";
|
||||
import type { DriverRegistrationStatusViewData } from "../view-data/DriverRegistrationStatusViewData";
|
||||
import { DriverRegistrationStatusDisplay } from "../display-objects/DriverRegistrationStatusDisplay";
|
||||
|
||||
export class DriverRegistrationStatusViewModel extends ViewModel {
|
||||
constructor(private readonly viewData: DriverRegistrationStatusViewData) {
|
||||
@@ -24,14 +24,14 @@ export class DriverRegistrationStatusViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get statusMessage(): string {
|
||||
return DriverRegistrationStatusDisplay.statusMessage(this.isRegistered);
|
||||
return DriverRegistrationStatusFormatter.statusMessage(this.isRegistered);
|
||||
}
|
||||
|
||||
get statusBadgeVariant(): string {
|
||||
return DriverRegistrationStatusDisplay.statusBadgeVariant(this.isRegistered);
|
||||
return DriverRegistrationStatusFormatter.statusBadgeVariant(this.isRegistered);
|
||||
}
|
||||
|
||||
get registrationButtonText(): string {
|
||||
return DriverRegistrationStatusDisplay.registrationButtonText(this.isRegistered);
|
||||
return DriverRegistrationStatusFormatter.registrationButtonText(this.isRegistered);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { NumberFormatter } from "../formatters/NumberFormatter";
|
||||
import { RatingFormatter } from "../formatters/RatingFormatter";
|
||||
import type { DriverSummaryData } from "../view-data/DriverSummaryData";
|
||||
import { NumberDisplay } from "../display-objects/NumberDisplay";
|
||||
import { RatingDisplay } from "../display-objects/RatingDisplay";
|
||||
|
||||
/**
|
||||
* View Model for driver summary with rating and rank
|
||||
@@ -30,7 +30,7 @@ export class DriverSummaryViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get ratingLabel(): string {
|
||||
return RatingDisplay.format(this.rating);
|
||||
return RatingFormatter.format(this.rating);
|
||||
}
|
||||
|
||||
get rank(): number | null {
|
||||
@@ -38,7 +38,7 @@ export class DriverSummaryViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get rankLabel(): string {
|
||||
return this.rank === null ? '—' : NumberDisplay.format(this.rank);
|
||||
return this.rank === null ? '—' : NumberFormatter.format(this.rank);
|
||||
}
|
||||
|
||||
get roleBadgeText(): string {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* Client-only UI helper built from ViewData.
|
||||
*/
|
||||
|
||||
import { ProfileDisplay } from "../display-objects/ProfileDisplay";
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { ProfileFormatter } from "../formatters/ProfileFormatter";
|
||||
import type { TeamDetailData } from "../view-data/TeamDetailViewData";
|
||||
|
||||
export class DriverTeamViewModel extends ViewModel {
|
||||
@@ -39,6 +39,6 @@ export class DriverTeamViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Display role */
|
||||
get displayRole(): string {
|
||||
return ProfileDisplay.getTeamRole(this.role).text;
|
||||
return ProfileFormatter.getTeamRole(this.role).text;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* Note: client-only ViewModel created from ViewData (never DTO).
|
||||
*/
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { RatingDisplay } from "../display-objects/RatingDisplay";
|
||||
import { RatingFormatter } from "../formatters/RatingFormatter";
|
||||
import type { DriverViewData } from "../view-data/DriverViewData";
|
||||
|
||||
export class DriverViewModel extends ViewModel {
|
||||
@@ -32,6 +32,6 @@ export class DriverViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted rating */
|
||||
get formattedRating(): string {
|
||||
return this.rating !== undefined ? RatingDisplay.format(this.rating) : "Unrated";
|
||||
return this.rating !== undefined ? RatingFormatter.format(this.rating) : "Unrated";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { LeagueRoleDisplay, LeagueRole } from "../display-objects/LeagueRoleDisplay";
|
||||
import { LeagueRole, LeagueRoleFormatter } from "../formatters/LeagueRoleFormatter";
|
||||
import type { LeagueMemberViewData } from "../view-data/LeagueMemberViewData";
|
||||
|
||||
export class LeagueMemberViewModel extends ViewModel {
|
||||
@@ -23,7 +23,7 @@ export class LeagueMemberViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Badge classes for role */
|
||||
get roleBadgeClasses(): string {
|
||||
return LeagueRoleDisplay.getLeagueRoleDisplay(this.role as LeagueRole)?.badgeClasses || '';
|
||||
return LeagueRoleFormatter.getLeagueRoleDisplay(this.role as LeagueRole)?.badgeClasses || '';
|
||||
}
|
||||
|
||||
/** UI-specific: Whether this member is the owner */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { LeagueTierDisplay } from "../display-objects/LeagueTierDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import { LeagueTierFormatter } from "../formatters/LeagueTierFormatter";
|
||||
import type { LeagueViewData } from "../view-data/LeagueDetailViewData";
|
||||
|
||||
export class LeagueViewModel extends ViewModel {
|
||||
@@ -50,7 +50,7 @@ export class LeagueViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get formattedMainSponsorCpm(): string {
|
||||
return CurrencyDisplay.format(this.mainSponsorCpm);
|
||||
return CurrencyFormatter.format(this.mainSponsorCpm);
|
||||
}
|
||||
|
||||
get racesLeft(): number {
|
||||
@@ -58,6 +58,6 @@ export class LeagueViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get tierConfig() {
|
||||
return LeagueTierDisplay.getDisplay(this.tier);
|
||||
return LeagueTierFormatter.getDisplay(this.tier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { WalletTransactionViewModel } from './WalletTransactionViewModel';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import type { LeagueWalletViewData } from "../view-data/LeagueWalletViewData";
|
||||
import { WalletTransactionViewModel } from './WalletTransactionViewModel';
|
||||
|
||||
export class LeagueWalletViewModel extends ViewModel {
|
||||
private readonly data: LeagueWalletViewData;
|
||||
@@ -24,22 +24,22 @@ export class LeagueWalletViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted balance */
|
||||
get formattedBalance(): string {
|
||||
return CurrencyDisplay.format(this.balance, this.currency);
|
||||
return CurrencyFormatter.format(this.balance, this.currency);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted total revenue */
|
||||
get formattedTotalRevenue(): string {
|
||||
return CurrencyDisplay.format(this.totalRevenue, this.currency);
|
||||
return CurrencyFormatter.format(this.totalRevenue, this.currency);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted total fees */
|
||||
get formattedTotalFees(): string {
|
||||
return CurrencyDisplay.format(this.totalFees, this.currency);
|
||||
return CurrencyFormatter.format(this.totalFees, this.currency);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted pending payouts */
|
||||
get formattedPendingPayouts(): string {
|
||||
return CurrencyDisplay.format(this.pendingPayouts, this.currency);
|
||||
return CurrencyFormatter.format(this.pendingPayouts, this.currency);
|
||||
}
|
||||
|
||||
/** UI-specific: Filtered transactions by type */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { DateDisplay } from "../display-objects/DateDisplay";
|
||||
import { MembershipFeeTypeDisplay } from "../display-objects/MembershipFeeTypeDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import { DateFormatter } from "../formatters/DateFormatter";
|
||||
import { MembershipFeeTypeFormatter } from "../formatters/MembershipFeeTypeFormatter";
|
||||
import type { MembershipFeeViewData } from "../view-data/MembershipFeeViewData";
|
||||
|
||||
export class MembershipFeeViewModel extends ViewModel {
|
||||
@@ -23,12 +23,12 @@ export class MembershipFeeViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted amount */
|
||||
get formattedAmount(): string {
|
||||
return CurrencyDisplay.format(this.amount, 'EUR');
|
||||
return CurrencyFormatter.format(this.amount, 'EUR');
|
||||
}
|
||||
|
||||
/** UI-specific: Type display */
|
||||
get typeDisplay(): string {
|
||||
return MembershipFeeTypeDisplay.format(this.type);
|
||||
return MembershipFeeTypeFormatter.format(this.type);
|
||||
}
|
||||
|
||||
/** UI-specific: Status display */
|
||||
@@ -43,11 +43,11 @@ export class MembershipFeeViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted created date */
|
||||
get formattedCreatedAt(): string {
|
||||
return DateDisplay.formatShort(this.createdAt);
|
||||
return DateFormatter.formatShort(this.createdAt);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted updated date */
|
||||
get formattedUpdatedAt(): string {
|
||||
return DateDisplay.formatShort(this.updatedAt);
|
||||
return DateFormatter.formatShort(this.updatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { DateDisplay } from "../display-objects/DateDisplay";
|
||||
import { PaymentTypeDisplay } from "../display-objects/PaymentTypeDisplay";
|
||||
import { PayerTypeDisplay } from "../display-objects/PayerTypeDisplay";
|
||||
import { StatusDisplay } from "../display-objects/StatusDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import { DateFormatter } from "../formatters/DateFormatter";
|
||||
import { PayerTypeFormatter } from "../formatters/PayerTypeFormatter";
|
||||
import { PaymentTypeFormatter } from "../formatters/PaymentTypeFormatter";
|
||||
import { StatusFormatter } from "../formatters/StatusFormatter";
|
||||
import type { PaymentViewData } from "../view-data/PaymentViewData";
|
||||
|
||||
export class PaymentViewModel extends ViewModel {
|
||||
@@ -29,12 +29,12 @@ export class PaymentViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted amount */
|
||||
get formattedAmount(): string {
|
||||
return CurrencyDisplay.format(this.amount, 'EUR');
|
||||
return CurrencyFormatter.format(this.amount, 'EUR');
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted net amount */
|
||||
get formattedNetAmount(): string {
|
||||
return CurrencyDisplay.format(this.netAmount, 'EUR');
|
||||
return CurrencyFormatter.format(this.netAmount, 'EUR');
|
||||
}
|
||||
|
||||
/** UI-specific: Status color */
|
||||
@@ -50,26 +50,26 @@ export class PaymentViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted created date */
|
||||
get formattedCreatedAt(): string {
|
||||
return DateDisplay.formatShort(this.createdAt);
|
||||
return DateFormatter.formatShort(this.createdAt);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted completed date */
|
||||
get formattedCompletedAt(): string {
|
||||
return this.completedAt ? DateDisplay.formatShort(this.completedAt) : 'Not completed';
|
||||
return this.completedAt ? DateFormatter.formatShort(this.completedAt) : 'Not completed';
|
||||
}
|
||||
|
||||
/** UI-specific: Status display */
|
||||
get statusDisplay(): string {
|
||||
return StatusDisplay.transactionStatus(this.status);
|
||||
return StatusFormatter.transactionStatus(this.status);
|
||||
}
|
||||
|
||||
/** UI-specific: Type display */
|
||||
get typeDisplay(): string {
|
||||
return PaymentTypeDisplay.format(this.type);
|
||||
return PaymentTypeFormatter.format(this.type);
|
||||
}
|
||||
|
||||
/** UI-specific: Payer type display */
|
||||
get payerTypeDisplay(): string {
|
||||
return PayerTypeDisplay.format(this.payerType);
|
||||
return PayerTypeFormatter.format(this.payerType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { FinishDisplay } from "../display-objects/FinishDisplay";
|
||||
import { DateDisplay } from "../display-objects/DateDisplay";
|
||||
import { PrizeTypeDisplay } from "../display-objects/PrizeTypeDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import { DateFormatter } from "../formatters/DateFormatter";
|
||||
import { FinishFormatter } from "../formatters/FinishFormatter";
|
||||
import { PrizeTypeFormatter } from "../formatters/PrizeTypeFormatter";
|
||||
import type { PrizeViewData } from "../view-data/PrizeViewData";
|
||||
|
||||
export class PrizeViewModel extends ViewModel {
|
||||
@@ -28,17 +28,17 @@ export class PrizeViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted amount */
|
||||
get formattedAmount(): string {
|
||||
return CurrencyDisplay.format(this.amount, 'EUR');
|
||||
return CurrencyFormatter.format(this.amount, 'EUR');
|
||||
}
|
||||
|
||||
/** UI-specific: Position display */
|
||||
get positionDisplay(): string {
|
||||
return FinishDisplay.format(this.position);
|
||||
return FinishFormatter.format(this.position);
|
||||
}
|
||||
|
||||
/** UI-specific: Type display */
|
||||
get typeDisplay(): string {
|
||||
return PrizeTypeDisplay.format(this.type);
|
||||
return PrizeTypeFormatter.format(this.type);
|
||||
}
|
||||
|
||||
/** UI-specific: Status display */
|
||||
@@ -58,11 +58,11 @@ export class PrizeViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted awarded date */
|
||||
get formattedAwardedAt(): string {
|
||||
return this.awardedAt ? DateDisplay.formatShort(this.awardedAt) : 'Not awarded';
|
||||
return this.awardedAt ? DateFormatter.formatShort(this.awardedAt) : 'Not awarded';
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted created date */
|
||||
get formattedCreatedAt(): string {
|
||||
return DateDisplay.formatShort(this.createdAt);
|
||||
return DateFormatter.formatShort(this.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { StatusDisplay } from '@/lib/display-objects/StatusDisplay';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { DateFormatter } from '@/lib/formatters/DateFormatter';
|
||||
import { StatusFormatter } from '@/lib/formatters/StatusFormatter';
|
||||
import type { ProtestViewData } from "@/lib/view-data/ProtestViewData";
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
|
||||
export class ProtestViewModel extends ViewModel {
|
||||
private readonly data: ProtestViewData;
|
||||
@@ -27,11 +27,11 @@ export class ProtestViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted submitted date */
|
||||
get formattedSubmittedAt(): string {
|
||||
return DateDisplay.formatShort(this.submittedAt);
|
||||
return DateFormatter.formatShort(this.submittedAt);
|
||||
}
|
||||
|
||||
/** UI-specific: Status display */
|
||||
get statusDisplay(): string {
|
||||
return StatusDisplay.protestStatus(this.status);
|
||||
return StatusFormatter.protestStatus(this.status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { DurationDisplay } from "../display-objects/DurationDisplay";
|
||||
import { DurationFormatter } from "../formatters/DurationFormatter";
|
||||
import type { RaceDetailUserResultViewData } from "../view-data/RaceDetailUserResultViewData";
|
||||
|
||||
export class RaceDetailUserResultViewModel extends ViewModel {
|
||||
@@ -54,6 +54,6 @@ export class RaceDetailUserResultViewModel extends ViewModel {
|
||||
/** UI-specific: Formatted lap time */
|
||||
get lapTimeFormatted(): string {
|
||||
if (this.fastestLap <= 0) return '--:--.---';
|
||||
return DurationDisplay.formatSeconds(this.fastestLap);
|
||||
return DurationFormatter.formatSeconds(this.fastestLap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { RaceStatusDisplay } from "../display-objects/RaceStatusDisplay";
|
||||
import { DateDisplay } from "../display-objects/DateDisplay";
|
||||
import { DateFormatter } from "../formatters/DateFormatter";
|
||||
import { RaceStatusFormatter } from "../formatters/RaceStatusFormatter";
|
||||
import type { RaceListItemViewData } from "../view-data/RaceListItemViewData";
|
||||
|
||||
export class RaceListItemViewModel extends ViewModel {
|
||||
@@ -29,12 +29,12 @@ export class RaceListItemViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted scheduled time */
|
||||
get formattedScheduledTime(): string {
|
||||
return DateDisplay.formatDateTime(this.scheduledAt);
|
||||
return DateFormatter.formatDateTime(this.scheduledAt);
|
||||
}
|
||||
|
||||
/** UI-specific: Badge variant for status */
|
||||
get statusBadgeVariant(): string {
|
||||
return RaceStatusDisplay.getVariant(this.status);
|
||||
return RaceStatusFormatter.getVariant(this.status);
|
||||
}
|
||||
|
||||
/** UI-specific: Time until start in minutes */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { FinishDisplay } from '../display-objects/FinishDisplay';
|
||||
import { DurationDisplay } from '../display-objects/DurationDisplay';
|
||||
import { DurationFormatter } from '../formatters/DurationFormatter';
|
||||
import { FinishFormatter } from '../formatters/FinishFormatter';
|
||||
import type { RaceResultViewData } from "../view-data/RaceResultViewData";
|
||||
|
||||
export class RaceResultViewModel extends ViewModel {
|
||||
@@ -50,7 +50,7 @@ export class RaceResultViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Badge for position */
|
||||
get positionBadge(): string {
|
||||
return FinishDisplay.format(this.position);
|
||||
return FinishFormatter.format(this.position);
|
||||
}
|
||||
|
||||
/** UI-specific: Color for incidents badge */
|
||||
@@ -63,7 +63,7 @@ export class RaceResultViewModel extends ViewModel {
|
||||
/** UI-specific: Formatted lap time */
|
||||
get lapTimeFormatted(): string {
|
||||
if (this.fastestLap <= 0) return '--:--.---';
|
||||
return DurationDisplay.formatSeconds(this.fastestLap);
|
||||
return DurationFormatter.formatSeconds(this.fastestLap);
|
||||
}
|
||||
|
||||
/** Required by ResultsTable */
|
||||
@@ -72,11 +72,11 @@ export class RaceResultViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get formattedPosition(): string {
|
||||
return FinishDisplay.format(this.position);
|
||||
return FinishFormatter.format(this.position);
|
||||
}
|
||||
|
||||
get formattedStartPosition(): string {
|
||||
return FinishDisplay.format(this.startPosition);
|
||||
return FinishFormatter.format(this.startPosition);
|
||||
}
|
||||
|
||||
get formattedIncidents(): string {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { DateDisplay } from "../display-objects/DateDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import { DateFormatter } from "../formatters/DateFormatter";
|
||||
import type { RenewalAlertViewData } from "../view-data/RenewalAlertViewData";
|
||||
|
||||
export class RenewalAlertViewModel extends ViewModel {
|
||||
@@ -20,11 +20,11 @@ export class RenewalAlertViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get formattedPrice(): string {
|
||||
return CurrencyDisplay.format(this.price);
|
||||
return CurrencyFormatter.format(this.price);
|
||||
}
|
||||
|
||||
get formattedRenewDate(): string {
|
||||
return DateDisplay.formatShort(this.renewDate);
|
||||
return DateFormatter.formatShort(this.renewDate);
|
||||
}
|
||||
|
||||
get typeIcon() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import type { SponsorshipDetailViewData } from "../view-data/SponsorshipDetailViewData";
|
||||
|
||||
export class SponsorshipDetailViewModel extends ViewModel {
|
||||
@@ -36,7 +36,7 @@ export class SponsorshipDetailViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted amount */
|
||||
get formattedAmount(): string {
|
||||
return CurrencyDisplay.format(this.amount, this.currency);
|
||||
return CurrencyFormatter.format(this.amount, this.currency);
|
||||
}
|
||||
|
||||
/** UI-specific: Tier badge variant */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import type { SponsorshipPricingViewData } from "../view-data/SponsorshipPricingViewData";
|
||||
|
||||
export class SponsorshipPricingViewModel extends ViewModel {
|
||||
@@ -16,12 +16,12 @@ export class SponsorshipPricingViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted main slot price */
|
||||
get formattedMainSlotPrice(): string {
|
||||
return CurrencyDisplay.format(this.mainSlotPrice, this.currency);
|
||||
return CurrencyFormatter.format(this.mainSlotPrice, this.currency);
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted secondary slot price */
|
||||
get formattedSecondarySlotPrice(): string {
|
||||
return CurrencyDisplay.format(this.secondarySlotPrice, this.currency);
|
||||
return CurrencyFormatter.format(this.secondarySlotPrice, this.currency);
|
||||
}
|
||||
|
||||
/** UI-specific: Price difference */
|
||||
@@ -31,7 +31,7 @@ export class SponsorshipPricingViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted price difference */
|
||||
get formattedPriceDifference(): string {
|
||||
return CurrencyDisplay.format(this.priceDifference, this.currency);
|
||||
return CurrencyFormatter.format(this.priceDifference, this.currency);
|
||||
}
|
||||
|
||||
/** UI-specific: Discount percentage for secondary slot */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { DateDisplay } from "../display-objects/DateDisplay";
|
||||
import { CurrencyFormatter } from "../formatters/CurrencyFormatter";
|
||||
import { DateFormatter } from "../formatters/DateFormatter";
|
||||
import type { SponsorshipRequestViewData } from "../view-data/SponsorshipRequestViewData";
|
||||
|
||||
export class SponsorshipRequestViewModel extends ViewModel {
|
||||
@@ -35,12 +35,12 @@ export class SponsorshipRequestViewModel extends ViewModel {
|
||||
|
||||
/** UI-specific: Formatted date */
|
||||
get formattedDate(): string {
|
||||
return DateDisplay.formatMonthDay(this.createdAt);
|
||||
return DateFormatter.formatMonthDay(this.createdAt);
|
||||
}
|
||||
|
||||
/** UI-specific: Net amount in dollars */
|
||||
get netAmountDollars(): string {
|
||||
return CurrencyDisplay.format(this.netAmount / 100, 'USD');
|
||||
return CurrencyFormatter.format(this.netAmount / 100, 'USD');
|
||||
}
|
||||
|
||||
/** UI-specific: Tier display */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from '../display-objects/CurrencyDisplay';
|
||||
import { DateDisplay } from '../display-objects/DateDisplay';
|
||||
import { NumberDisplay } from '../display-objects/NumberDisplay';
|
||||
import { CurrencyFormatter } from '../formatters/CurrencyFormatter';
|
||||
import { DateFormatter } from '../formatters/DateFormatter';
|
||||
import { NumberFormatter } from '../formatters/NumberFormatter';
|
||||
import type { SponsorshipViewData } from "../view-data/SponsorshipViewData";
|
||||
|
||||
/**
|
||||
@@ -52,11 +52,11 @@ export class SponsorshipViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get formattedImpressions(): string {
|
||||
return NumberDisplay.format(this.impressions);
|
||||
return NumberFormatter.format(this.impressions);
|
||||
}
|
||||
|
||||
get formattedPrice(): string {
|
||||
return CurrencyDisplay.format(this.price);
|
||||
return CurrencyFormatter.format(this.price);
|
||||
}
|
||||
|
||||
get daysRemaining(): number {
|
||||
@@ -92,8 +92,8 @@ export class SponsorshipViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
get periodDisplay(): string {
|
||||
const start = DateDisplay.formatMonthYear(this.startDate);
|
||||
const end = DateDisplay.formatMonthYear(this.endDate);
|
||||
const start = DateFormatter.formatMonthYear(this.startDate);
|
||||
const end = DateFormatter.formatMonthYear(this.endDate);
|
||||
return `${start} - ${end}`;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user