Files
2026-10-25 22:55:29 +04:00

87 lines
4.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
);
}