@bytebrand/fe-ui-core-autobahn 1.0.101 → 1.0.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/utils/CommonUtils.ts +17 -0
- package/utils/index.ts +1 -0
- package/vdp/VdpModules.tsx +29 -6
- package/vehiclesProps/decoratedProps.tsx +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.103",
|
|
4
4
|
"description": "auto.de md3 (Autobahn) design system — UI primitives, cards, chrome, VDP modules, theme CSS and locales. Ships raw TypeScript source (like fe-ui-core before it); each consumer's bundler compiles it.",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bytebrand/car-schema-selectors": "^2.0.7",
|
|
13
|
-
"@bytebrand/fe-financing": "^5.14.
|
|
13
|
+
"@bytebrand/fe-financing": "^5.14.15",
|
|
14
14
|
"classnames": "2.2.6",
|
|
15
15
|
"clsx": "^2.1.1",
|
|
16
16
|
"d3-format": "1.4.5",
|
package/utils/CommonUtils.ts
CHANGED
|
@@ -25,6 +25,23 @@ export const formatMileage = (millage: number | string) => {
|
|
|
25
25
|
return millage.toLocaleString('en-US').replace(/,/g, '.');
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
// Same thresholds as the "NEU" badge (ADV-166, see fe-main/fe-landing/fe-wp-widgets
|
|
29
|
+
// DataTransformers.ts) minus its extra "recently listed" (modificationDate) check —
|
|
30
|
+
// this is about the car itself being practically brand-new, not about listing recency.
|
|
31
|
+
// Missing firstRegistration passes the age check (can't disqualify what we don't know).
|
|
32
|
+
const EFFECTIVELY_NEW_MAX_MILEAGE_KM = 1000;
|
|
33
|
+
const EFFECTIVELY_NEW_MAX_AGE_MONTHS = 12;
|
|
34
|
+
const AVG_MS_PER_MONTH = 1000 * 60 * 60 * 24 * 30.44;
|
|
35
|
+
|
|
36
|
+
export const isEffectivelyNewCar = (mileageKm?: number | string | null, firstRegistration?: number | Date | string | null): boolean => {
|
|
37
|
+
if (!(Number(mileageKm) < EFFECTIVELY_NEW_MAX_MILEAGE_KM)) return false;
|
|
38
|
+
if (!firstRegistration) return true;
|
|
39
|
+
const registeredAtMs = firstRegistration instanceof Date ? firstRegistration.getTime() : Number(firstRegistration) * 1000;
|
|
40
|
+
if (!Number.isFinite(registeredAtMs)) return true;
|
|
41
|
+
const ageMonths = (Date.now() - registeredAtMs) / AVG_MS_PER_MONTH;
|
|
42
|
+
return ageMonths < EFFECTIVELY_NEW_MAX_AGE_MONTHS;
|
|
43
|
+
};
|
|
44
|
+
|
|
28
45
|
export const getFormattedNumber = (num: number) => Number.isFinite(num) ? num.toLocaleString('de-DE') : null;
|
|
29
46
|
|
|
30
47
|
export const numberWithDot = (value: number | string) => {
|
package/utils/index.ts
CHANGED
package/vdp/VdpModules.tsx
CHANGED
|
@@ -1845,6 +1845,11 @@ export interface FinanceDetailsModalProps {
|
|
|
1845
1845
|
/** car make+model — mobile sheet subtitle under the title (mockup). */
|
|
1846
1846
|
vehicleName?: string;
|
|
1847
1847
|
price: number;
|
|
1848
|
+
/** "No package" reference price (common.basePrice = originalSalesPrice + margin) — the
|
|
1849
|
+
* Repräsentatives Beispiel's Fahrzeugpreis/Anschaffungspreis line uses THIS, not `price`
|
|
1850
|
+
* (which is the real, package-dependent Kaufen/rate-basis price shown everywhere else).
|
|
1851
|
+
* Falls back to `price` when not supplied. */
|
|
1852
|
+
basePrice?: number;
|
|
1848
1853
|
monthly: number;
|
|
1849
1854
|
/** struck old rate (oldMonthlyInstallment, isStrikeShown-gated) — red line-through next to the rate. */
|
|
1850
1855
|
oldMonthly?: number;
|
|
@@ -1859,8 +1864,14 @@ export interface FinanceDetailsModalProps {
|
|
|
1859
1864
|
netLoanAmount?: number;
|
|
1860
1865
|
/** Schlussrate (balloon final installment); shown only when > 0. */
|
|
1861
1866
|
finalInstallment?: number;
|
|
1862
|
-
/** Gesamt(rückzahlungs)betrag from the config; shown only when > 0 (never the wrong monthly·term).
|
|
1867
|
+
/** Gesamt(rückzahlungs)betrag from the config; shown only when > 0 (never the wrong monthly·term).
|
|
1868
|
+
* Drives the bold "Gesamtbelastung inkl. Anzahlung" grand total ONLY — the "Gesamtbetrag
|
|
1869
|
+
* Finanzierung" disclosure line itself uses totalFinancingAmount below instead. */
|
|
1863
1870
|
grossAmount?: number;
|
|
1871
|
+
/** getFinancing's totalFinancingAmount = (paybackPeriod-1)×monthlyCarPrice + finalInstallment —
|
|
1872
|
+
* the simpler Repräsentatives-Beispiel total (uniform Folgerate, no separate 1.Rate), financing
|
|
1873
|
+
* only. Falls back to grossAmount when not supplied. */
|
|
1874
|
+
totalFinancingAmount?: number;
|
|
1864
1875
|
/** Sollzinssatz p.a. (%); shown only when > 0. */
|
|
1865
1876
|
nominalRate?: number;
|
|
1866
1877
|
/** slider bounds — when the change handlers are supplied, the modal shows adjustment sliders. */
|
|
@@ -1921,6 +1932,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
1921
1932
|
mode,
|
|
1922
1933
|
vehicleName,
|
|
1923
1934
|
price,
|
|
1935
|
+
basePrice,
|
|
1924
1936
|
monthly,
|
|
1925
1937
|
oldMonthly,
|
|
1926
1938
|
listPrice,
|
|
@@ -1931,6 +1943,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
1931
1943
|
netLoanAmount,
|
|
1932
1944
|
finalInstallment,
|
|
1933
1945
|
grossAmount,
|
|
1946
|
+
totalFinancingAmount,
|
|
1934
1947
|
nominalRate,
|
|
1935
1948
|
downMin,
|
|
1936
1949
|
downMax,
|
|
@@ -2091,7 +2104,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2091
2104
|
const subtotalWithPackage = ratesSum != null ? ratesSum + down + (hasAddServicesRow ? addServicesRowValue : 0) : undefined;
|
|
2092
2105
|
const grandTotal = subtotalWithPackage != null ? subtotalWithPackage + residual : undefined;
|
|
2093
2106
|
rows = [
|
|
2094
|
-
[t('autobahn:modal.leaseVehiclePrice'), `${eurCents(price)} €`],
|
|
2107
|
+
[t('autobahn:modal.leaseVehiclePrice'), `${eurCents(basePrice ?? price)} €`],
|
|
2095
2108
|
[t('autobahn:modal.term'), t('autobahn:modal.months', { months: term, count: term })],
|
|
2096
2109
|
...(annualMileage != null ? ([[t('autobahn:modal.annualMileage'), `${eur(annualMileage)} km`]] as Array<[string, string]>) : []),
|
|
2097
2110
|
[t('autobahn:modal.leaseDownPayment'), `${eurCents(down)} €`],
|
|
@@ -2117,19 +2130,29 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2117
2130
|
...(listPrice != null && listPrice > 0 ? ([[t('autobahn:modal.listPriceLease'), `${eurCents(listPrice)} €`]] as Array<[string, string]>) : []),
|
|
2118
2131
|
];
|
|
2119
2132
|
} else {
|
|
2133
|
+
// Ballonfinanzierung: `term` includes the Schlussrate as its final slot, so only
|
|
2134
|
+
// (term-1) of them are regular Folgerate-value installments. Without a balloon, all
|
|
2135
|
+
// `term` payments are regular — no adjustment.
|
|
2136
|
+
const hasBalloon = finalInstallment != null && finalInstallment > 0;
|
|
2137
|
+
const regularRateCount = hasBalloon ? term - 1 : term;
|
|
2138
|
+
// Repräsentatives-Beispiel total for THIS line only — falls back to grossAmount when the
|
|
2139
|
+
// page hasn't been updated to pass totalFinancingAmount yet.
|
|
2140
|
+
const totalRepaymentValue = totalFinancingAmount ?? grossAmount;
|
|
2120
2141
|
rows = [
|
|
2121
|
-
[t('autobahn:modal.vehiclePrice'), `${eurCents(price)} €`],
|
|
2142
|
+
[t('autobahn:modal.vehiclePrice'), `${eurCents(basePrice ?? price)} €`],
|
|
2122
2143
|
[t('autobahn:modal.term'), t('autobahn:modal.months', { months: term, count: term })],
|
|
2123
2144
|
[t('autobahn:modal.downPayment'), `${eurCents(down)} €`],
|
|
2124
|
-
[t('autobahn:modal.monthlyRateCount', { count:
|
|
2125
|
-
...(
|
|
2145
|
+
[t('autobahn:modal.monthlyRateCount', { count: regularRateCount }), `${eurCents(monthly)} €`],
|
|
2146
|
+
...(hasBalloon ? ([[t('autobahn:modal.finalInstallment'), `${eurCents(finalInstallment)} €`]] as Array<[string, string]>) : []),
|
|
2126
2147
|
...(hasAddServicesRow
|
|
2127
2148
|
? ([[t('autobahn:modal.addServicesRow'), `${eurCents(addServicesRowValue)} €`]] as Array<[string, string]>)
|
|
2128
2149
|
: []),
|
|
2129
2150
|
...(nominalRate != null && nominalRate > 0 ? ([[t('autobahn:modal.nominalRate'), pct(nominalRate)]] as Array<[string, string]>) : []),
|
|
2130
2151
|
[t('autobahn:modal.effectiveRate'), pct(apr)],
|
|
2131
2152
|
[t('autobahn:modal.netLoanAmount'), `${eurCents(netto)} €`],
|
|
2132
|
-
...(
|
|
2153
|
+
...(totalRepaymentValue != null && totalRepaymentValue > 0
|
|
2154
|
+
? ([[t('autobahn:modal.totalRepayment'), `${eurCents(totalRepaymentValue)} €`]] as Array<[string, string]>)
|
|
2155
|
+
: []),
|
|
2133
2156
|
...(totalCost != null
|
|
2134
2157
|
? ([[hasAddServices || hasAddServicesRow ? t('autobahn:modal.totalCostWithPackage') : t('autobahn:modal.totalCost'), `${eurCents(totalCost)} €`, true]] as Array<
|
|
2135
2158
|
[string, string, boolean]
|
|
@@ -4,7 +4,7 @@ import moment from 'moment';
|
|
|
4
4
|
import Icon from '../compat/IconContainer/IconContainer';
|
|
5
5
|
import CO2Efficiency from '../compat/Co2Widget/CO2Efficiency';
|
|
6
6
|
import { formatTimestamp } from '../utils/DateUtils';
|
|
7
|
-
import { formatMileage } from '../utils/CommonUtils';
|
|
7
|
+
import { formatMileage, isEffectivelyNewCar } from '../utils/CommonUtils';
|
|
8
8
|
import { EMISSION_STICKERS_ICONS, WP_EMISSION_STICKERS_ICONS } from '../constants/Search';
|
|
9
9
|
import { DELIVERY_PERIODS_EXTRA } from '../constants';
|
|
10
10
|
import { IDecoratedProp, ICar, IFinancingUtils } from '../types/types';
|
|
@@ -1120,7 +1120,10 @@ const getDecoratedProps = (
|
|
|
1120
1120
|
maintenanceGuide: {
|
|
1121
1121
|
title: t('vehicleProps:title.maintenanceGuide'),
|
|
1122
1122
|
get value() {
|
|
1123
|
-
|
|
1123
|
+
// A practically brand-new car (near-zero mileage, recent/no first registration)
|
|
1124
|
+
// is always treated as full-service-history, regardless of the raw dealer flag —
|
|
1125
|
+
// there's simply been no time yet for a service history to matter.
|
|
1126
|
+
const fullserviceHistory = isEffectivelyNewCar(mileage, firstRegistration) ? true : car.careService.fullServiceHistory;
|
|
1124
1127
|
return isPropDefined(fullserviceHistory)
|
|
1125
1128
|
? (fullserviceHistory ? t('vehicleProps:value.yes') : t('vehicleProps:value.no'))
|
|
1126
1129
|
: t('vehicleProps:value.na');
|