Files
spq-v2/src/lib/quoteHelpers.ts
T
2026-07-12 10:01:39 -04:00

64 lines
3.2 KiB
TypeScript

/* ── Pure helpers extracted from QuoteGenerator.tsx ── */
/** Recursively strip null values from service objects (Zod v4 optional() rejects null). */
export function sanitizeServices(svcs: any[]): any[] {
return svcs.map((s) => {
const clean: Record<string, any> = {};
for (const [k, v] of Object.entries(s)) {
if (v === null) continue;
clean[k] = v;
}
return clean;
});
}
export function normalizeCustomerInfo(raw: any) {
if (!raw || typeof raw !== 'object') return null;
const parsedNestedCustomerInfo = (() => {
const nested = raw.customerInfo;
if (!nested) return null;
if (typeof nested === 'string') {
try { return JSON.parse(nested); } catch { return null; }
}
return typeof nested === 'object' ? nested : null;
})();
const pick = (...values: any[]) => {
for (const value of values) {
if (typeof value === 'string' && value.trim()) return value;
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
}
return '';
};
return {
name: pick(raw.name, parsedNestedCustomerInfo?.name, raw.customerName, parsedNestedCustomerInfo?.customerName, raw.customer, parsedNestedCustomerInfo?.customer),
phone: pick(raw.phone, parsedNestedCustomerInfo?.phone, raw.customerPhone, parsedNestedCustomerInfo?.customerPhone, raw.customer_phone, parsedNestedCustomerInfo?.customer_phone),
vehicleInfo: pick(raw.vehicleInfo, parsedNestedCustomerInfo?.vehicleInfo, raw.vehicle, parsedNestedCustomerInfo?.vehicle, raw.vehicle_info, parsedNestedCustomerInfo?.vehicle_info),
vin: pick(raw.vin, parsedNestedCustomerInfo?.vin, raw.VIN, parsedNestedCustomerInfo?.VIN),
mileage: pick(raw.mileage, parsedNestedCustomerInfo?.mileage, raw.miles, parsedNestedCustomerInfo?.miles),
roNumber: pick(raw.roNumber, parsedNestedCustomerInfo?.roNumber, raw.repairOrderNumber, parsedNestedCustomerInfo?.repairOrderNumber, raw.RO, parsedNestedCustomerInfo?.RO, raw.ro, parsedNestedCustomerInfo?.ro, raw.ro_number, parsedNestedCustomerInfo?.ro_number),
repairOrderId: pick(raw.repairOrderId, parsedNestedCustomerInfo?.repairOrderId),
serviceAdvisor: pick(raw.serviceAdvisor, parsedNestedCustomerInfo?.serviceAdvisor, raw.advisorName, parsedNestedCustomerInfo?.advisorName, raw.serviceAdvisorName, parsedNestedCustomerInfo?.serviceAdvisorName, raw.advisor, parsedNestedCustomerInfo?.advisor),
};
}
export function formatQuoteCurrency(amount: number): string {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
}
export function formatQuoteDate(iso: string | undefined | null): string {
if (!iso) return '—';
try {
return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).format(new Date(iso));
} catch { return '—'; }
}
export function recentQuoteDate(quote: { createdAt?: string; lastUpdated?: string }): string {
return quote.createdAt || quote.lastUpdated || '';
}
export function recentQuoteTimestamp(quote: { createdAt?: string; lastUpdated?: string }): number {
const value = recentQuoteDate(quote);
const parsed = value ? new Date(value).getTime() : NaN;
return Number.isNaN(parsed) ? 0 : parsed;
}