@bytebrand/fe-ui-core-autobahn 1.0.61 → 1.0.62
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/cards/CarRow.tsx +62 -17
- package/cards/cardParts.tsx +7 -3
- package/constants.ts +31 -31
- package/package.json +1 -1
- package/theme/md3.css +19 -2
- package/utils/financeFormat.ts +62 -62
- package/utils/priceRating.ts +58 -58
package/cards/CarRow.tsx
CHANGED
|
@@ -107,14 +107,28 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
107
107
|
{car.isNew && <span className="neu-badge">NEU</span>}
|
|
108
108
|
{car.drop != null ? <DropChip t={t} amount={car.drop} /> : showSave && <SaveChip t={t} amount={car.savings!} />}
|
|
109
109
|
<ZeroDownChip t={t} show={car.zeroDown} compact className="show-mobile" />
|
|
110
|
-
<ZeroDownChip
|
|
110
|
+
<ZeroDownChip
|
|
111
|
+
t={t}
|
|
112
|
+
show={car.zeroDown}
|
|
113
|
+
className="hide-mobile"
|
|
114
|
+
style={{
|
|
115
|
+
height: 30,
|
|
116
|
+
padding: '0 10px',
|
|
117
|
+
borderRadius: 15,
|
|
118
|
+
background: 'rgba(0, 92, 255, 0.2)',
|
|
119
|
+
border: '1px solid rgba(0, 92, 255, 0.05)',
|
|
120
|
+
color: '#005CFF',
|
|
121
|
+
fontSize: 13,
|
|
122
|
+
fontWeight: 700,
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
111
125
|
<PercentChip pct={car.dropPct} />
|
|
112
126
|
</div>
|
|
113
127
|
<OfferBadge show={showOfferBadge} t={t} />
|
|
114
128
|
</div>
|
|
115
|
-
{/*
|
|
116
|
-
|
|
117
|
-
<div style={{ position: 'absolute', bottom: 10, left: 10, zIndex: 3 }}>
|
|
129
|
+
{/* mobile: expand icon + "current|total" pill, bottom-left; desktop uses its own lighter
|
|
130
|
+
counter chip further down (combined with the eye/heart chip). */}
|
|
131
|
+
<div className="show-mobile" style={{ position: 'absolute', bottom: 10, left: 10, zIndex: 3 }}>
|
|
118
132
|
<GalleryCounter current={photoIndex + 1} total={car.photos} />
|
|
119
133
|
</div>
|
|
120
134
|
<div
|
|
@@ -124,27 +138,58 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
124
138
|
>
|
|
125
139
|
<IconButton name="heart" filled active={isFavorite} onClick={onToggleFavorite} label={t('autobahn:card.action.save')} />
|
|
126
140
|
</div>
|
|
127
|
-
{/*
|
|
141
|
+
{/* activity/trust badges — mobile only (dark photo-count pill + eye/heart/trust pills);
|
|
142
|
+
desktop replaces this whole row with the combined light chips below (Figma "mcard" —
|
|
143
|
+
the heart itself lives in the info column's title row on desktop, per the Figma export,
|
|
144
|
+
not on the photo). */}
|
|
145
|
+
<div className="show-mobile">
|
|
146
|
+
<GalleryBadges car={car} t={t} />
|
|
147
|
+
</div>
|
|
148
|
+
{/* desktop: single row — light "1|N" photo counter + a combined eye/heart chip, no
|
|
149
|
+
availability/Geprüft pill (not present in the Figma "mcard" export). */}
|
|
128
150
|
<div
|
|
129
|
-
onClick={(e) => e.stopPropagation()}
|
|
130
151
|
className="hide-mobile"
|
|
131
|
-
style={{ position: 'absolute',
|
|
152
|
+
style={{ position: 'absolute', bottom: 12, left: 12, zIndex: 3, display: 'flex', alignItems: 'center', gap: 6 }}
|
|
132
153
|
>
|
|
133
|
-
|
|
154
|
+
{car.photos > 1 && (
|
|
155
|
+
<span className="mcard-photo-chip">
|
|
156
|
+
<Icon name="maximize" size={16} stroke={1.7} />
|
|
157
|
+
{photoIndex + 1}|{car.photos}
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
160
|
+
{(!!car.viewsCount || !!car.favoritesCount) && (
|
|
161
|
+
<span className="mcard-photo-chip">
|
|
162
|
+
{!!car.viewsCount && (
|
|
163
|
+
<>
|
|
164
|
+
<Icon name="eye" size={16} stroke={1.7} />
|
|
165
|
+
{car.viewsCount}
|
|
166
|
+
</>
|
|
167
|
+
)}
|
|
168
|
+
{!!car.favoritesCount && (
|
|
169
|
+
<>
|
|
170
|
+
<Icon name="heart" size={16} stroke={1.7} />
|
|
171
|
+
{car.favoritesCount}
|
|
172
|
+
</>
|
|
173
|
+
)}
|
|
174
|
+
</span>
|
|
175
|
+
)}
|
|
134
176
|
</div>
|
|
135
|
-
{/* activity/trust badges — overlaid on the studio-white floor at the bottom of the photo
|
|
136
|
-
itself (PM ref), not a separate divided strip below it. Bottom-right, raised above the
|
|
137
|
-
carousel dots and the photo-count badge (positioning lives in .gallery-badges, md3.css). */}
|
|
138
|
-
<GalleryBadges car={car} t={t} />
|
|
139
177
|
</div>
|
|
140
178
|
|
|
141
179
|
<div className="listcard-info">
|
|
142
|
-
<div style={{
|
|
143
|
-
<div
|
|
144
|
-
{
|
|
180
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12 }}>
|
|
181
|
+
<div style={{ minWidth: 0 }}>
|
|
182
|
+
<div className="md-title" style={{ fontSize: 18, lineHeight: 1.2 }}>
|
|
183
|
+
{car.make} {car.model}
|
|
184
|
+
</div>
|
|
185
|
+
<div className="md-body-sm" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
186
|
+
{car.trim}
|
|
187
|
+
</div>
|
|
145
188
|
</div>
|
|
146
|
-
|
|
147
|
-
|
|
189
|
+
{/* desktop only — the Figma export places the heart here (title row), not on the photo;
|
|
190
|
+
mobile keeps its own heart on the photo (show-mobile, above), untouched. */}
|
|
191
|
+
<div onClick={(e) => e.stopPropagation()} className="hide-mobile">
|
|
192
|
+
<IconButton name="heart" active={isFavorite} onClick={onToggleFavorite} label={t('autobahn:card.action.save')} />
|
|
148
193
|
</div>
|
|
149
194
|
</div>
|
|
150
195
|
<div className="specstrip">
|
package/cards/cardParts.tsx
CHANGED
|
@@ -246,12 +246,15 @@ export const ZeroDownChip: React.FunctionComponent<{
|
|
|
246
246
|
compact?: boolean;
|
|
247
247
|
t: TranslationFunction;
|
|
248
248
|
className?: string;
|
|
249
|
-
|
|
249
|
+
/** overrides the default pill look (e.g. the desktop "mcard" full-text badge's own blue tint/size
|
|
250
|
+
* from the Figma export) — merged last so it wins over the shared dealPill defaults below. */
|
|
251
|
+
style?: React.CSSProperties;
|
|
252
|
+
}> = ({ show, compact, t, className, style }) => {
|
|
250
253
|
if (!show) return null;
|
|
251
254
|
return (
|
|
252
255
|
<span
|
|
253
256
|
className={className}
|
|
254
|
-
style={{ ...dealPill, background: 'var(--md-accent-container)', color: 'var(--md-on-accent-container)' }}
|
|
257
|
+
style={{ ...dealPill, background: 'var(--md-accent-container)', color: 'var(--md-on-accent-container)', ...style }}
|
|
255
258
|
>
|
|
256
259
|
{compact ? t('autobahn:card.price.zeroDownCompact') : t('autobahn:card.price.zeroDown')}
|
|
257
260
|
</span>
|
|
@@ -894,7 +897,8 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, c
|
|
|
894
897
|
firstInstallment: leasFirstInstallment,
|
|
895
898
|
months: leasMonths,
|
|
896
899
|
mileage: leasMileageStr,
|
|
897
|
-
})}
|
|
900
|
+
})}{' '}
|
|
901
|
+
· <span className="pp-details-link">{t('autobahn:card.price.details')}</span>
|
|
898
902
|
</div>
|
|
899
903
|
)}
|
|
900
904
|
</div>
|
package/constants.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
// Autobahn (auto.de redesign) — shared lookup constants barrel.
|
|
2
|
-
// Pure data re-exported from ./constants/autobahn (kept import-safe for plain Node consumers,
|
|
3
|
-
// see that file's header) plus the legacy fe-ui-core shim re-exports below.
|
|
4
|
-
export * from './constants/autobahn';
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Legacy shared constants (moved here from fe-ui-core).
|
|
8
|
-
//
|
|
9
|
-
// This block mirrors the public symbol set of the old
|
|
10
|
-
// `@bytebrand/fe-ui-core/constants` barrel so consumers can swap that
|
|
11
|
-
// specifier for `@bytebrand/fe-ui-core-autobahn/constants` 1:1. The
|
|
12
|
-
// implementations live under ./constants/ (core.ts, app.ts, price.ts,
|
|
13
|
-
// Search.ts, …); fe-ui-core re-exports them via shims until it is deleted.
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
offers,
|
|
18
|
-
checkoutOffers,
|
|
19
|
-
checkoutWarranties,
|
|
20
|
-
checkoutSteps,
|
|
21
|
-
checkoutSubStep,
|
|
22
|
-
DELIVERY_PERIODS_EXTRA,
|
|
23
|
-
THREE,
|
|
24
|
-
WLTP_ENERGY_EFFICIENCY_CLASS
|
|
25
|
-
} from './constants/core';
|
|
26
|
-
|
|
27
|
-
export { CUSTOM_ERROR_MESSAGES, supportPhone, supportPhoneLink } from './constants/app';
|
|
28
|
-
|
|
29
|
-
export { priceRatings } from './constants/price';
|
|
30
|
-
|
|
31
|
-
export { THROTTLE_DELAY } from './constants/Search';
|
|
1
|
+
// Autobahn (auto.de redesign) — shared lookup constants barrel.
|
|
2
|
+
// Pure data re-exported from ./constants/autobahn (kept import-safe for plain Node consumers,
|
|
3
|
+
// see that file's header) plus the legacy fe-ui-core shim re-exports below.
|
|
4
|
+
export * from './constants/autobahn';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Legacy shared constants (moved here from fe-ui-core).
|
|
8
|
+
//
|
|
9
|
+
// This block mirrors the public symbol set of the old
|
|
10
|
+
// `@bytebrand/fe-ui-core/constants` barrel so consumers can swap that
|
|
11
|
+
// specifier for `@bytebrand/fe-ui-core-autobahn/constants` 1:1. The
|
|
12
|
+
// implementations live under ./constants/ (core.ts, app.ts, price.ts,
|
|
13
|
+
// Search.ts, …); fe-ui-core re-exports them via shims until it is deleted.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
offers,
|
|
18
|
+
checkoutOffers,
|
|
19
|
+
checkoutWarranties,
|
|
20
|
+
checkoutSteps,
|
|
21
|
+
checkoutSubStep,
|
|
22
|
+
DELIVERY_PERIODS_EXTRA,
|
|
23
|
+
THREE,
|
|
24
|
+
WLTP_ENERGY_EFFICIENCY_CLASS
|
|
25
|
+
} from './constants/core';
|
|
26
|
+
|
|
27
|
+
export { CUSTOM_ERROR_MESSAGES, supportPhone, supportPhoneLink } from './constants/app';
|
|
28
|
+
|
|
29
|
+
export { priceRatings } from './constants/price';
|
|
30
|
+
|
|
31
|
+
export { THROTTLE_DELAY } from './constants/Search';
|
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.62",
|
|
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",
|
package/theme/md3.css
CHANGED
|
@@ -3932,8 +3932,25 @@ line-height: 31px;
|
|
|
3932
3932
|
font-weight: 600;
|
|
3933
3933
|
white-space: nowrap;
|
|
3934
3934
|
}
|
|
3935
|
-
/*
|
|
3936
|
-
"Push Chips / Likes"
|
|
3935
|
+
/* desktop-only "mcard" (CarRow/SRP) photo-count / eye+heart chip (Figma "Vehicle Gallery / Photos
|
|
3936
|
+
Button" and "Push Chips / Likes") — lighter/more translucent than .gallery-pill (which stays
|
|
3937
|
+
mobile-only, used by .gallery-badges above), and can hold two icon+count pairs in one pill. */
|
|
3938
|
+
.md3 .mcard-photo-chip {
|
|
3939
|
+
display: inline-flex;
|
|
3940
|
+
align-items: center;
|
|
3941
|
+
gap: 6px;
|
|
3942
|
+
height: 30px;
|
|
3943
|
+
padding: 0 10px;
|
|
3944
|
+
border-radius: 16px;
|
|
3945
|
+
border: 1px solid #e5eaf0;
|
|
3946
|
+
background: rgba(245, 247, 250, 0.5);
|
|
3947
|
+
color: #111820;
|
|
3948
|
+
font-size: 12px;
|
|
3949
|
+
font-weight: 700;
|
|
3950
|
+
white-space: nowrap;
|
|
3951
|
+
}
|
|
3952
|
+
/* Car Card (grid) gallery overlay pills (Figma "Vehicle Gallery / Photos Button" node 281:1016
|
|
3953
|
+
and "Push Chips / Likes" node 297:1037) — light glass pill, distinct from the dark `.imglabel`
|
|
3937
3954
|
used elsewhere (VDP main gallery) and from the older `.gallery-pill` iteration above. */
|
|
3938
3955
|
.md3 .cc-photo-pill {
|
|
3939
3956
|
display: inline-flex;
|
package/utils/financeFormat.ts
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
// Autobahn (auto.de redesign) — shared financing-number formatting.
|
|
2
|
-
//
|
|
3
|
-
// Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
|
|
4
|
-
// Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
|
|
5
|
-
// second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
|
|
6
|
-
// Node service.
|
|
7
|
-
//
|
|
8
|
-
// The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
|
|
9
|
-
// financing means monthly×term is NOT the total) and lives here once, not per-caller.
|
|
10
|
-
|
|
11
|
-
/** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
|
|
12
|
-
* toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
|
|
13
|
-
export function formatEur(n: number): string {
|
|
14
|
-
return n.toLocaleString('de-DE');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
|
|
18
|
-
export function formatPct(n: number): string {
|
|
19
|
-
return `${n.toFixed(2).replace('.', ',')} %`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface FinanceExampleFields {
|
|
23
|
-
price: number;
|
|
24
|
-
apr: number;
|
|
25
|
-
down: number;
|
|
26
|
-
/** Real config figure — falls back to price-down ONLY when genuinely absent. */
|
|
27
|
-
netLoanAmount?: number;
|
|
28
|
-
nominalRate?: number;
|
|
29
|
-
finalInstallment?: number;
|
|
30
|
-
grossAmount?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface FinanceExampleLabels {
|
|
34
|
-
vehiclePrice: string;
|
|
35
|
-
priceSuffix: string;
|
|
36
|
-
apr: string;
|
|
37
|
-
nominal: string;
|
|
38
|
-
net: string;
|
|
39
|
-
down: string;
|
|
40
|
-
final: string;
|
|
41
|
-
gross: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
|
|
46
|
-
* included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
|
|
47
|
-
* derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
|
|
48
|
-
* `exampleLine`, byte-for-byte).
|
|
49
|
-
*/
|
|
50
|
-
export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
|
|
51
|
-
const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
|
|
52
|
-
const parts: string[] = [
|
|
53
|
-
`${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
|
|
54
|
-
...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
|
|
55
|
-
...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
|
|
56
|
-
`${labels.net}: ${formatEur(netto)} €`,
|
|
57
|
-
`${labels.down}: ${formatEur(fields.down)} €`,
|
|
58
|
-
...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
|
|
59
|
-
...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
|
|
60
|
-
];
|
|
61
|
-
return `${footnoteMarker} ${parts.join('; ')};`;
|
|
62
|
-
}
|
|
1
|
+
// Autobahn (auto.de redesign) — shared financing-number formatting.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
|
|
4
|
+
// Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
|
|
5
|
+
// second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
|
|
6
|
+
// Node service.
|
|
7
|
+
//
|
|
8
|
+
// The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
|
|
9
|
+
// financing means monthly×term is NOT the total) and lives here once, not per-caller.
|
|
10
|
+
|
|
11
|
+
/** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
|
|
12
|
+
* toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
|
|
13
|
+
export function formatEur(n: number): string {
|
|
14
|
+
return n.toLocaleString('de-DE');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
|
|
18
|
+
export function formatPct(n: number): string {
|
|
19
|
+
return `${n.toFixed(2).replace('.', ',')} %`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FinanceExampleFields {
|
|
23
|
+
price: number;
|
|
24
|
+
apr: number;
|
|
25
|
+
down: number;
|
|
26
|
+
/** Real config figure — falls back to price-down ONLY when genuinely absent. */
|
|
27
|
+
netLoanAmount?: number;
|
|
28
|
+
nominalRate?: number;
|
|
29
|
+
finalInstallment?: number;
|
|
30
|
+
grossAmount?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FinanceExampleLabels {
|
|
34
|
+
vehiclePrice: string;
|
|
35
|
+
priceSuffix: string;
|
|
36
|
+
apr: string;
|
|
37
|
+
nominal: string;
|
|
38
|
+
net: string;
|
|
39
|
+
down: string;
|
|
40
|
+
final: string;
|
|
41
|
+
gross: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
|
|
46
|
+
* included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
|
|
47
|
+
* derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
|
|
48
|
+
* `exampleLine`, byte-for-byte).
|
|
49
|
+
*/
|
|
50
|
+
export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
|
|
51
|
+
const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
|
|
52
|
+
const parts: string[] = [
|
|
53
|
+
`${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
|
|
54
|
+
...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
|
|
55
|
+
...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
|
|
56
|
+
`${labels.net}: ${formatEur(netto)} €`,
|
|
57
|
+
`${labels.down}: ${formatEur(fields.down)} €`,
|
|
58
|
+
...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
|
|
59
|
+
...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
|
|
60
|
+
];
|
|
61
|
+
return `${footnoteMarker} ${parts.join('; ')};`;
|
|
62
|
+
}
|
package/utils/priceRating.ts
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
// Autobahn (auto.de redesign) — Preisbewertung rating math, extracted from CommonUtils.ts.
|
|
2
|
-
//
|
|
3
|
-
// Pulled out on purpose: CommonUtils.ts transitively imports `../constants/Search`, which
|
|
4
|
-
// `require()`s image assets (webpack-only — breaks any plain Node `require`/`tsc` consumer).
|
|
5
|
-
// This file only imports `../constants/price` (zero further imports) — a pure, testable leaf
|
|
6
|
-
// module. CommonUtils.ts re-exports these for existing consumers — one implementation, not a fork.
|
|
7
|
-
// (image-generator's Meta AIA renderer vendors a copy of this math directly instead of depending
|
|
8
|
-
// on this package — see services/image-generator/src/services/metaAia/vendoredAutobahnMath.ts —
|
|
9
|
-
// so a plain-Node backend doesn't need to npm-install this whole design system's dependency tree.)
|
|
10
|
-
import { priceRatings, priceRatingConfig } from '../constants/price';
|
|
11
|
-
import { PREDICTABLE_PRICE_INDEX } from '../constants/core';
|
|
12
|
-
|
|
13
|
-
export const getPriceRatingConfig = (currentPrice: number, pricePredicted: number) => { // tslint:disable-line
|
|
14
|
-
const round = (value: number) => {
|
|
15
|
-
return Number.isFinite(pricePredicted)
|
|
16
|
-
? Math.round((pricePredicted * (1 + (value / 100))) / 100) * 100 // tslint:disable-line
|
|
17
|
-
: '???';
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
if (currentPrice === 0) {
|
|
21
|
-
console.log('Empty current price');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return Object.keys(priceRatingConfig).reduce((acc: any, key: any) => {
|
|
25
|
-
const { min, max } = priceRatingConfig[key];
|
|
26
|
-
return { ...acc, [key]: { min: round(min), max: round(max) } };
|
|
27
|
-
}, {}); // tslint:disable-line
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const getPredictablePrice = (currentSalesPrice: number, predictablePrice: number) => {
|
|
31
|
-
if (Number.isFinite(Number.parseFloat(`${predictablePrice}`))) {
|
|
32
|
-
return predictablePrice;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return currentSalesPrice * PREDICTABLE_PRICE_INDEX;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const getPriceRating = (currentSalesPrice: number, predictablePrice: number) => {
|
|
39
|
-
const isCorrectCurrentSalesPrice = Number.isFinite(Number.parseFloat(`${currentSalesPrice}`));
|
|
40
|
-
// const isCorrectPredictablePrice = Number.isFinite(Number.parseFloat(`${predictablePrice}`));
|
|
41
|
-
|
|
42
|
-
if (!isCorrectCurrentSalesPrice) return 0;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const validatedPredictablePrice = getPredictablePrice(currentSalesPrice, predictablePrice);
|
|
46
|
-
|
|
47
|
-
const ratingConfig = getPriceRatingConfig(currentSalesPrice, validatedPredictablePrice);
|
|
48
|
-
|
|
49
|
-
for (const key of Object.keys(ratingConfig)) {
|
|
50
|
-
const { min, max } = ratingConfig[key];
|
|
51
|
-
const TOP_PRICE = 5;
|
|
52
|
-
if (key === 'highPrice' && currentSalesPrice === max) return 1;
|
|
53
|
-
if (key === 'topPrice' && currentSalesPrice <= min) return TOP_PRICE;
|
|
54
|
-
if (currentSalesPrice >= min && currentSalesPrice < max) return priceRatings.indexOf(key);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return 0;
|
|
58
|
-
};
|
|
1
|
+
// Autobahn (auto.de redesign) — Preisbewertung rating math, extracted from CommonUtils.ts.
|
|
2
|
+
//
|
|
3
|
+
// Pulled out on purpose: CommonUtils.ts transitively imports `../constants/Search`, which
|
|
4
|
+
// `require()`s image assets (webpack-only — breaks any plain Node `require`/`tsc` consumer).
|
|
5
|
+
// This file only imports `../constants/price` (zero further imports) — a pure, testable leaf
|
|
6
|
+
// module. CommonUtils.ts re-exports these for existing consumers — one implementation, not a fork.
|
|
7
|
+
// (image-generator's Meta AIA renderer vendors a copy of this math directly instead of depending
|
|
8
|
+
// on this package — see services/image-generator/src/services/metaAia/vendoredAutobahnMath.ts —
|
|
9
|
+
// so a plain-Node backend doesn't need to npm-install this whole design system's dependency tree.)
|
|
10
|
+
import { priceRatings, priceRatingConfig } from '../constants/price';
|
|
11
|
+
import { PREDICTABLE_PRICE_INDEX } from '../constants/core';
|
|
12
|
+
|
|
13
|
+
export const getPriceRatingConfig = (currentPrice: number, pricePredicted: number) => { // tslint:disable-line
|
|
14
|
+
const round = (value: number) => {
|
|
15
|
+
return Number.isFinite(pricePredicted)
|
|
16
|
+
? Math.round((pricePredicted * (1 + (value / 100))) / 100) * 100 // tslint:disable-line
|
|
17
|
+
: '???';
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (currentPrice === 0) {
|
|
21
|
+
console.log('Empty current price');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Object.keys(priceRatingConfig).reduce((acc: any, key: any) => {
|
|
25
|
+
const { min, max } = priceRatingConfig[key];
|
|
26
|
+
return { ...acc, [key]: { min: round(min), max: round(max) } };
|
|
27
|
+
}, {}); // tslint:disable-line
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const getPredictablePrice = (currentSalesPrice: number, predictablePrice: number) => {
|
|
31
|
+
if (Number.isFinite(Number.parseFloat(`${predictablePrice}`))) {
|
|
32
|
+
return predictablePrice;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return currentSalesPrice * PREDICTABLE_PRICE_INDEX;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const getPriceRating = (currentSalesPrice: number, predictablePrice: number) => {
|
|
39
|
+
const isCorrectCurrentSalesPrice = Number.isFinite(Number.parseFloat(`${currentSalesPrice}`));
|
|
40
|
+
// const isCorrectPredictablePrice = Number.isFinite(Number.parseFloat(`${predictablePrice}`));
|
|
41
|
+
|
|
42
|
+
if (!isCorrectCurrentSalesPrice) return 0;
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const validatedPredictablePrice = getPredictablePrice(currentSalesPrice, predictablePrice);
|
|
46
|
+
|
|
47
|
+
const ratingConfig = getPriceRatingConfig(currentSalesPrice, validatedPredictablePrice);
|
|
48
|
+
|
|
49
|
+
for (const key of Object.keys(ratingConfig)) {
|
|
50
|
+
const { min, max } = ratingConfig[key];
|
|
51
|
+
const TOP_PRICE = 5;
|
|
52
|
+
if (key === 'highPrice' && currentSalesPrice === max) return 1;
|
|
53
|
+
if (key === 'topPrice' && currentSalesPrice <= min) return TOP_PRICE;
|
|
54
|
+
if (currentSalesPrice >= min && currentSalesPrice < max) return priceRatings.indexOf(key);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return 0;
|
|
58
|
+
};
|