view data fixes
This commit is contained in:
@@ -2,24 +2,39 @@
|
||||
* Billing View Model
|
||||
*
|
||||
* View model for sponsor billing data with UI-specific transformations.
|
||||
* Transforms BillingViewData into UI-ready state with formatting and derived fields.
|
||||
*/
|
||||
export class BillingViewModel {
|
||||
import type { BillingViewData } from '@/lib/view-data/BillingViewData';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { CurrencyDisplay } from "../display-objects/CurrencyDisplay";
|
||||
import { DateDisplay } from "../display-objects/DateDisplay";
|
||||
|
||||
/**
|
||||
* BillingViewModel
|
||||
*
|
||||
* View Model for sponsor billing data.
|
||||
* Transforms BillingViewData into UI-ready state with formatting and derived fields.
|
||||
*/
|
||||
export class BillingViewModel extends ViewModel {
|
||||
paymentMethods: PaymentMethodViewModel[];
|
||||
invoices: InvoiceViewModel[];
|
||||
stats: BillingStatsViewModel;
|
||||
|
||||
constructor(data: {
|
||||
paymentMethods: unknown[];
|
||||
invoices: unknown[];
|
||||
stats: unknown;
|
||||
}) {
|
||||
this.paymentMethods = data.paymentMethods.map(pm => new PaymentMethodViewModel(pm));
|
||||
this.invoices = data.invoices.map(inv => new InvoiceViewModel(inv));
|
||||
this.stats = new BillingStatsViewModel(data.stats);
|
||||
constructor(viewData: BillingViewData) {
|
||||
super();
|
||||
this.paymentMethods = viewData.paymentMethods.map(pm => new PaymentMethodViewModel(pm));
|
||||
this.invoices = viewData.invoices.map(inv => new InvoiceViewModel(inv));
|
||||
this.stats = new BillingStatsViewModel(viewData.stats);
|
||||
}
|
||||
}
|
||||
|
||||
export class PaymentMethodViewModel {
|
||||
/**
|
||||
* PaymentMethodViewModel
|
||||
*
|
||||
* View Model for payment method data.
|
||||
* Provides formatted display labels and expiry information.
|
||||
*/
|
||||
export class PaymentMethodViewModel extends ViewModel {
|
||||
id: string;
|
||||
type: 'card' | 'bank' | 'sepa';
|
||||
last4: string;
|
||||
@@ -29,35 +44,43 @@ export class PaymentMethodViewModel {
|
||||
expiryYear?: number;
|
||||
bankName?: string;
|
||||
|
||||
constructor(data: unknown) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const d = data as any;
|
||||
this.id = d.id;
|
||||
this.type = d.type;
|
||||
this.last4 = d.last4;
|
||||
this.brand = d.brand;
|
||||
this.isDefault = d.isDefault;
|
||||
this.expiryMonth = d.expiryMonth;
|
||||
this.expiryYear = d.expiryYear;
|
||||
this.bankName = d.bankName;
|
||||
}
|
||||
// UI-specific derived fields (primitive outputs only)
|
||||
readonly displayLabel: string;
|
||||
readonly expiryDisplay: string | null;
|
||||
|
||||
get displayLabel(): string {
|
||||
if (this.type === 'sepa' && this.bankName) {
|
||||
return `${this.bankName} •••• ${this.last4}`;
|
||||
}
|
||||
return `${this.brand} •••• ${this.last4}`;
|
||||
}
|
||||
|
||||
get expiryDisplay(): string | null {
|
||||
if (this.expiryMonth && this.expiryYear) {
|
||||
return `${String(this.expiryMonth).padStart(2, '0')}/${this.expiryYear}`;
|
||||
}
|
||||
return null;
|
||||
constructor(viewData: {
|
||||
id: string;
|
||||
type: 'card' | 'bank' | 'sepa';
|
||||
last4: string;
|
||||
brand?: string;
|
||||
isDefault: boolean;
|
||||
expiryMonth?: number;
|
||||
expiryYear?: number;
|
||||
bankName?: string;
|
||||
displayLabel: string;
|
||||
expiryDisplay: string | null;
|
||||
}) {
|
||||
super();
|
||||
this.id = viewData.id;
|
||||
this.type = viewData.type;
|
||||
this.last4 = viewData.last4;
|
||||
this.brand = viewData.brand;
|
||||
this.isDefault = viewData.isDefault;
|
||||
this.expiryMonth = viewData.expiryMonth;
|
||||
this.expiryYear = viewData.expiryYear;
|
||||
this.bankName = viewData.bankName;
|
||||
this.displayLabel = viewData.displayLabel;
|
||||
this.expiryDisplay = viewData.expiryDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
export class InvoiceViewModel {
|
||||
/**
|
||||
* InvoiceViewModel
|
||||
*
|
||||
* View Model for invoice data.
|
||||
* Provides formatted amounts, dates, and derived status flags.
|
||||
*/
|
||||
export class InvoiceViewModel extends ViewModel {
|
||||
id: string;
|
||||
invoiceNumber: string;
|
||||
date: Date;
|
||||
@@ -70,40 +93,55 @@ export class InvoiceViewModel {
|
||||
sponsorshipType: 'league' | 'team' | 'driver' | 'race' | 'platform';
|
||||
pdfUrl: string;
|
||||
|
||||
constructor(data: unknown) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const d = data as any;
|
||||
this.id = d.id;
|
||||
this.invoiceNumber = d.invoiceNumber;
|
||||
this.date = new Date(d.date);
|
||||
this.dueDate = new Date(d.dueDate);
|
||||
this.amount = d.amount;
|
||||
this.vatAmount = d.vatAmount;
|
||||
this.totalAmount = d.totalAmount;
|
||||
this.status = d.status;
|
||||
this.description = d.description;
|
||||
this.sponsorshipType = d.sponsorshipType;
|
||||
this.pdfUrl = d.pdfUrl;
|
||||
}
|
||||
// UI-specific derived fields (primitive outputs only)
|
||||
readonly formattedTotalAmount: string;
|
||||
readonly formattedVatAmount: string;
|
||||
readonly formattedDate: string;
|
||||
readonly isOverdue: boolean;
|
||||
|
||||
get formattedTotalAmount(): string {
|
||||
return `€${this.totalAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`;
|
||||
}
|
||||
|
||||
get formattedVatAmount(): string {
|
||||
return `€${this.vatAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`;
|
||||
}
|
||||
|
||||
get formattedDate(): string {
|
||||
return this.date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
|
||||
get isOverdue(): boolean {
|
||||
return this.status === 'overdue' || (this.status === 'pending' && new Date() > this.dueDate);
|
||||
constructor(viewData: {
|
||||
id: string;
|
||||
invoiceNumber: string;
|
||||
date: string;
|
||||
dueDate: string;
|
||||
amount: number;
|
||||
vatAmount: number;
|
||||
totalAmount: number;
|
||||
status: 'paid' | 'pending' | 'overdue' | 'failed';
|
||||
description: string;
|
||||
sponsorshipType: 'league' | 'team' | 'driver' | 'race' | 'platform';
|
||||
pdfUrl: string;
|
||||
formattedTotalAmount: string;
|
||||
formattedVatAmount: string;
|
||||
formattedDate: string;
|
||||
isOverdue: boolean;
|
||||
}) {
|
||||
super();
|
||||
this.id = viewData.id;
|
||||
this.invoiceNumber = viewData.invoiceNumber;
|
||||
this.date = new Date(viewData.date);
|
||||
this.dueDate = new Date(viewData.dueDate);
|
||||
this.amount = viewData.amount;
|
||||
this.vatAmount = viewData.vatAmount;
|
||||
this.totalAmount = viewData.totalAmount;
|
||||
this.status = viewData.status;
|
||||
this.description = viewData.description;
|
||||
this.sponsorshipType = viewData.sponsorshipType;
|
||||
this.pdfUrl = viewData.pdfUrl;
|
||||
this.formattedTotalAmount = viewData.formattedTotalAmount;
|
||||
this.formattedVatAmount = viewData.formattedVatAmount;
|
||||
this.formattedDate = viewData.formattedDate;
|
||||
this.isOverdue = viewData.isOverdue;
|
||||
}
|
||||
}
|
||||
|
||||
export class BillingStatsViewModel {
|
||||
/**
|
||||
* BillingStatsViewModel
|
||||
*
|
||||
* View Model for billing statistics.
|
||||
* Provides formatted monetary fields and derived metrics.
|
||||
*/
|
||||
export class BillingStatsViewModel extends ViewModel {
|
||||
totalSpent: number;
|
||||
pendingAmount: number;
|
||||
nextPaymentDate: Date;
|
||||
@@ -111,34 +149,37 @@ export class BillingStatsViewModel {
|
||||
activeSponsorships: number;
|
||||
averageMonthlySpend: number;
|
||||
|
||||
constructor(data: unknown) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const d = data as any;
|
||||
this.totalSpent = d.totalSpent;
|
||||
this.pendingAmount = d.pendingAmount;
|
||||
this.nextPaymentDate = new Date(d.nextPaymentDate);
|
||||
this.nextPaymentAmount = d.nextPaymentAmount;
|
||||
this.activeSponsorships = d.activeSponsorships;
|
||||
this.averageMonthlySpend = d.averageMonthlySpend;
|
||||
}
|
||||
// UI-specific derived fields (primitive outputs only)
|
||||
readonly formattedTotalSpent: string;
|
||||
readonly formattedPendingAmount: string;
|
||||
readonly formattedNextPaymentAmount: string;
|
||||
readonly formattedAverageMonthlySpend: string;
|
||||
readonly formattedNextPaymentDate: string;
|
||||
|
||||
get formattedTotalSpent(): string {
|
||||
return `€${this.totalSpent.toLocaleString('de-DE')}`;
|
||||
}
|
||||
|
||||
get formattedPendingAmount(): string {
|
||||
return `€${this.pendingAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`;
|
||||
}
|
||||
|
||||
get formattedNextPaymentAmount(): string {
|
||||
return `€${this.nextPaymentAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })}`;
|
||||
}
|
||||
|
||||
get formattedAverageMonthlySpend(): string {
|
||||
return `€${this.averageMonthlySpend.toLocaleString('de-DE')}`;
|
||||
}
|
||||
|
||||
get formattedNextPaymentDate(): string {
|
||||
return this.nextPaymentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
constructor(viewData: {
|
||||
totalSpent: number;
|
||||
pendingAmount: number;
|
||||
nextPaymentDate: string;
|
||||
nextPaymentAmount: number;
|
||||
activeSponsorships: number;
|
||||
averageMonthlySpend: number;
|
||||
formattedTotalSpent: string;
|
||||
formattedPendingAmount: string;
|
||||
formattedNextPaymentAmount: string;
|
||||
formattedAverageMonthlySpend: string;
|
||||
formattedNextPaymentDate: string;
|
||||
}) {
|
||||
super();
|
||||
this.totalSpent = viewData.totalSpent;
|
||||
this.pendingAmount = viewData.pendingAmount;
|
||||
this.nextPaymentDate = new Date(viewData.nextPaymentDate);
|
||||
this.nextPaymentAmount = viewData.nextPaymentAmount;
|
||||
this.activeSponsorships = viewData.activeSponsorships;
|
||||
this.averageMonthlySpend = viewData.averageMonthlySpend;
|
||||
this.formattedTotalSpent = viewData.formattedTotalSpent;
|
||||
this.formattedPendingAmount = viewData.formattedPendingAmount;
|
||||
this.formattedNextPaymentAmount = viewData.formattedNextPaymentAmount;
|
||||
this.formattedAverageMonthlySpend = viewData.formattedAverageMonthlySpend;
|
||||
this.formattedNextPaymentDate = viewData.formattedNextPaymentDate;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user