Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
87 lines
4.4 KiB
TypeScript
87 lines
4.4 KiB
TypeScript
'use client';
|
||
|
||
import { discountFor, quote, type Locale } from '@barev/core';
|
||
import { formatAmd, tFromMessages, type Messages } from '@barev/i18n';
|
||
import { Stepper } from '@barev/ui';
|
||
import { useMemo, useState } from 'react';
|
||
import type { CleaningConfig } from '../lib/data';
|
||
|
||
const TYPES = ['regular', 'general', 'renovation'] as const;
|
||
|
||
/**
|
||
* Калькулятор экономии с Barev+: цена уборки — функцией quote, как на сервере, скидка — как у участника Barev+.
|
||
* Подписка вычитается из экономии; если выгоды нет, так и пишем и показываем, с какого числа уборок она появится.
|
||
*/
|
||
export function Savings({ locale, config, monthlyPrice, messages }: { locale: Locale; config: CleaningConfig; monthlyPrice: number; messages: Messages }) {
|
||
const t = useMemo(() => tFromMessages(locale, messages), [locale, messages]);
|
||
const amd = (n: number) => formatAmd(locale, n);
|
||
// стартовый сценарий: генеральная уборка трёшки дважды в месяц — дальше человек подставляет своё
|
||
const [type, setType] = useState<(typeof TYPES)[number]>('general');
|
||
const [rooms, setRooms] = useState(3);
|
||
const [count, setCount] = useState(2);
|
||
|
||
const pct = discountFor('BAREV_PLUS', config.categoryDiscountPct);
|
||
const params = { cleaningType: type, rooms, bathrooms: 1 };
|
||
const one = quote(config.pricing, params);
|
||
const oneWithPlus = quote(config.pricing, params, pct);
|
||
const without = one.totalPrice * count;
|
||
const withPlus = oneWithPlus.totalPrice * count + monthlyPrice;
|
||
const save = without - withPlus;
|
||
const perCleaning = one.totalPrice - oneWithPlus.totalPrice;
|
||
// выгода есть, когда скидка за n уборок больше цены подписки: n × perCleaning > monthlyPrice
|
||
const breakeven = perCleaning > 0 ? Math.floor(monthlyPrice / perCleaning) + 1 : null;
|
||
const max = Math.max(without, withPlus);
|
||
|
||
return (
|
||
<div className="v5-calc">
|
||
<div className="v5-inputs">
|
||
<div className="v5-seg" role="radiogroup" aria-label={t('params.cleaning.cleaningType.label')}>
|
||
{TYPES.map((v) => (
|
||
<button key={v} type="button" role="radio" aria-checked={type === v} onClick={() => setType(v)}>
|
||
{t(`params.cleaning.cleaningType.${v}`)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
<div className="v5-row">
|
||
<span>{t('params.cleaning.rooms.label')}</span>
|
||
<Stepper value={rooms} min={1} max={6} onChange={setRooms} decLabel={t('common.decrease')} incLabel={t('common.increase')} />
|
||
</div>
|
||
<div className="v5-row">
|
||
<span>{t('landing.v5.perMonth')}</span>
|
||
<Stepper value={count} min={1} max={8} onChange={setCount} decLabel={t('common.decrease')} incLabel={t('common.increase')} />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="v5-result" aria-live="polite">
|
||
<div className="v5-save">
|
||
<span className="v5-save__label">{save > 0 ? t('landing.v5.save') : t('landing.v5.noSave')}</span>
|
||
<span className="v5-save__num">{save > 0 ? amd(save) : '0 ֏'}</span>
|
||
{save > 0 && <span className="v5-save__year">{t('landing.v5.perYear', { amount: amd(save * 12) })}</span>}
|
||
</div>
|
||
<div className="v5-bars">
|
||
<div className="v5-bar">
|
||
<span className="v5-bar__label">{t('landing.v5.without')}</span>
|
||
<span className="v5-bar__track">
|
||
<span className="v5-bar__fill v5-bar__fill--without" style={{ width: `${(without / max) * 100}%` }} />
|
||
</span>
|
||
<span className="v5-bar__num">{amd(without)}</span>
|
||
</div>
|
||
<div className="v5-bar">
|
||
<span className="v5-bar__label">
|
||
{t('landing.v5.with')}
|
||
<small>{t('landing.v5.withNote', { price: amd(monthlyPrice) })}</small>
|
||
</span>
|
||
<span className="v5-bar__track">
|
||
<span className="v5-bar__fill v5-bar__fill--with" style={{ width: `${(withPlus / max) * 100}%` }} />
|
||
</span>
|
||
<span className="v5-bar__num">{amd(withPlus)}</span>
|
||
</div>
|
||
</div>
|
||
{breakeven !== null && (
|
||
<p className="v5-breakeven">{breakeven === 1 ? t('landing.v5.breakevenFirst') : t('landing.v5.breakeven', { count: breakeven })}</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|