421 lines
16 KiB
TypeScript
421 lines
16 KiB
TypeScript
/* eslint-disable react-hooks/exhaustive-deps */
|
||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||
import Slider from "react-rangeslider";
|
||
import Button from "./Button";
|
||
import CalcSelect from "./CalcSelect";
|
||
import ArrowRightIcon from "./icons/ArrowRightIcon";
|
||
import regionsData from "../assets/regionsData.json";
|
||
import { useEffect, useState } from "react";
|
||
import CloseIcon from "./icons/CloseIcon";
|
||
|
||
interface Region {
|
||
id: number;
|
||
name: string;
|
||
areaInComplex: number;
|
||
areaApartment: number;
|
||
costPerSquare: number;
|
||
}
|
||
|
||
function Calc() {
|
||
const [consultations, setConsultations] = useState<number>(100);
|
||
const [selectedRegion, setSelectedRegion] = useState<Region>();
|
||
const [implementationPeriod, setImplementationPeriod] = useState<number>();
|
||
const [oldImplementationPeriod, setOldImplementationPeriod] =
|
||
useState<number>();
|
||
const [monthlyIncome, setMonthlyIncome] = useState<number>();
|
||
const [oldMonthlyIncome, setOldMonthlyIncome] = useState<number>();
|
||
const [isToolEnabled, setIsToolEnabled] = useState<boolean>(false);
|
||
const [reservation, setReservation] = useState<number>();
|
||
const [oldReservation, setOldReservation] = useState<number>();
|
||
const [sales, setSales] = useState<number>();
|
||
const [oldSales, setOldSales] = useState<number>();
|
||
const [diffImplementationPeriod, setDiffImplementationPeriod] =
|
||
useState<number>();
|
||
const [diffMonthlyIncome, setDiffMonthlyIncome] = useState<number>();
|
||
const [implementationPeriodEnding, setImplementationPeriodEnding] =
|
||
useState<string>();
|
||
const [oldImplementationPeriodEnding, setOldImplementationPeriodEnding] =
|
||
useState<string>();
|
||
const [diffImplementationPeriodEnding, setDiffImplementationPeriodEnding] =
|
||
useState<string>();
|
||
|
||
useEffect(() => {
|
||
setSelectedRegion(regionsData.find((region) => region.id === 11));
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (!consultations || !selectedRegion || !sales || !oldSales) return;
|
||
|
||
setOldImplementationPeriod(
|
||
Math.round(
|
||
selectedRegion.areaInComplex / selectedRegion.areaApartment / oldSales
|
||
)
|
||
);
|
||
setImplementationPeriod(
|
||
Math.round(
|
||
selectedRegion.areaInComplex / selectedRegion.areaApartment / sales
|
||
)
|
||
);
|
||
|
||
setOldMonthlyIncome(
|
||
Math.round(
|
||
(selectedRegion.areaApartment *
|
||
selectedRegion.costPerSquare *
|
||
oldSales) /
|
||
1000
|
||
)
|
||
);
|
||
setMonthlyIncome(
|
||
Math.round(
|
||
(selectedRegion.areaApartment * selectedRegion.costPerSquare * sales) /
|
||
1000
|
||
)
|
||
);
|
||
}, [consultations, selectedRegion, isToolEnabled, sales]);
|
||
|
||
useEffect(() => {
|
||
setOldReservation(Math.round((30 * consultations) / 100));
|
||
setReservation(Math.round((48 * consultations) / 100));
|
||
|
||
setOldSales(Math.round((((30 * consultations) / 100) * 30) / 100));
|
||
setSales(Math.round((((48 * consultations) / 100) * 42) / 100));
|
||
}, [consultations]);
|
||
|
||
useEffect(() => {
|
||
if (!implementationPeriod || !oldImplementationPeriod) return;
|
||
|
||
setDiffImplementationPeriod(oldImplementationPeriod - implementationPeriod);
|
||
}, [implementationPeriod, oldImplementationPeriod]);
|
||
|
||
useEffect(() => {
|
||
if (!monthlyIncome || !oldMonthlyIncome) return;
|
||
|
||
setDiffMonthlyIncome(monthlyIncome - oldMonthlyIncome);
|
||
}, [monthlyIncome, oldMonthlyIncome]);
|
||
|
||
useEffect(() => {
|
||
if (!implementationPeriod) return;
|
||
|
||
if (implementationPeriod > 10 && implementationPeriod < 15) {
|
||
setOldImplementationPeriodEnding("месяцев");
|
||
return;
|
||
}
|
||
|
||
if (implementationPeriod % 10 === 1) {
|
||
setImplementationPeriodEnding("месяц");
|
||
} else if (
|
||
implementationPeriod % 10 === 2 ||
|
||
implementationPeriod % 10 === 3 ||
|
||
implementationPeriod % 10 === 4
|
||
) {
|
||
setImplementationPeriodEnding("месяца");
|
||
} else {
|
||
setImplementationPeriodEnding("месяцев");
|
||
}
|
||
}, [implementationPeriod]);
|
||
|
||
useEffect(() => {
|
||
if (!oldImplementationPeriod) return;
|
||
|
||
if (oldImplementationPeriod > 10 && oldImplementationPeriod < 15) {
|
||
setOldImplementationPeriodEnding("месяцев");
|
||
return;
|
||
}
|
||
|
||
if (oldImplementationPeriod % 10 === 1) {
|
||
setOldImplementationPeriodEnding("месяц");
|
||
} else if (
|
||
oldImplementationPeriod % 10 === 2 ||
|
||
oldImplementationPeriod % 10 === 3 ||
|
||
oldImplementationPeriod % 10 === 4
|
||
) {
|
||
setOldImplementationPeriodEnding("месяца");
|
||
} else {
|
||
setOldImplementationPeriodEnding("месяцев");
|
||
}
|
||
}, [oldImplementationPeriod]);
|
||
|
||
useEffect(() => {
|
||
if (!diffImplementationPeriod) return;
|
||
|
||
if (diffImplementationPeriod > 10 && diffImplementationPeriod < 15) {
|
||
setDiffImplementationPeriodEnding("месяцев");
|
||
return;
|
||
}
|
||
|
||
if (diffImplementationPeriod % 10 === 1) {
|
||
setDiffImplementationPeriodEnding("месяц");
|
||
} else if (
|
||
diffImplementationPeriod % 10 === 2 ||
|
||
diffImplementationPeriod % 10 === 3 ||
|
||
diffImplementationPeriod % 10 === 4
|
||
) {
|
||
setDiffImplementationPeriodEnding("месяца");
|
||
} else {
|
||
setDiffImplementationPeriodEnding("месяцев");
|
||
}
|
||
}, [diffImplementationPeriod]);
|
||
|
||
return (
|
||
<div className="relative flex flex-col sm:gap-16 gap-8">
|
||
<div className="grid xl:grid-cols-4 sm:grid-cols-3">
|
||
<div className="xl:col-auto sm:col-span-full xl:block sm:grid grid-cols-2">
|
||
<CalcSelect
|
||
label="Регион"
|
||
placeholder="Выберите регион"
|
||
defaultOption={regionsData.find((region) => region.id === 11)?.name}
|
||
options={regionsData.map((regionItem) => regionItem.name)}
|
||
handleSelect={(option) => {
|
||
const foundRegion = regionsData.find(
|
||
(region) => region.name === option
|
||
);
|
||
|
||
if (foundRegion) {
|
||
setSelectedRegion(foundRegion);
|
||
}
|
||
}}
|
||
/>
|
||
<div className="border xl:border-t-0 xl:border-l sm:border-l-0 sm:border-t border-t-0 border-[#3D425C] 2xl:p-6 p-4 flex items-center">
|
||
<p className="text-[#52587A] text-xs leading-[120%]">
|
||
Установлены усредненные показатели по региону.
|
||
<br className="2xl:block xl:hidden sm:block hidden" /> Источник:{" "}
|
||
<a
|
||
href="https://наш.дом.рф"
|
||
target="_blank"
|
||
className="text-[#798FFF]"
|
||
>
|
||
наш.дом.рф
|
||
</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="2xl:px-8 2xl:py-6 px-6 py-4 flex flex-col justify-between gap-6 border xl:border-l-0 xl:border-t border-t-0 border-[#3D425C]">
|
||
<p className="text-sm">
|
||
Средняя площадь
|
||
<br />
|
||
жилья в комплексе, м²
|
||
</p>
|
||
<p className="2xl:text-5xl text-[32px] font-gilroy font-medium leading-none">
|
||
{(selectedRegion?.areaInComplex || 1500).toLocaleString()}
|
||
</p>
|
||
</div>
|
||
<div className="2xl:px-8 2xl:py-6 px-6 py-4 flex flex-col justify-between gap-6 border sm:border-l-0 border-l xl:border-t border-t-0 border-[#3D425C]">
|
||
<p className="text-sm">
|
||
Средняя площадь
|
||
<br />
|
||
квартиры, м²
|
||
</p>
|
||
<p className="2xl:text-5xl text-[32px] font-gilroy font-medium leading-none">
|
||
{(selectedRegion?.areaApartment || 100).toLocaleString()}
|
||
</p>
|
||
</div>
|
||
<div className="2xl:px-8 2xl:py-6 px-6 py-4 flex flex-col justify-between gap-6 border sm:border-l-0 border-l xl:border-t border-t-0 border-[#3D425C]">
|
||
<p className="text-sm">
|
||
Средняя стоимость
|
||
<br />
|
||
одного м², тыс. руб.
|
||
</p>
|
||
<p className="2xl:text-5xl text-[32px] font-gilroy font-medium leading-none">
|
||
{(selectedRegion?.costPerSquare || 100).toLocaleString()}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid xl:grid-cols-4 grid-cols-2">
|
||
<div className="xl:flex flex-col xl:gap-6 xl:mr-8 xl:border-b border-[#3D425C] xl:col-auto col-span-full sm:grid grid-cols-2 gap-12 xl:mb-0 sm:mb-6 mb-8">
|
||
<div className="flex flex-col gap-6">
|
||
<div className="flex justify-between">
|
||
<p className="font-gilroy font-medium 2xl:text-base text-sm leading-none">
|
||
Очных консультаций в месяц
|
||
</p>
|
||
<p className="font-gilroy font-medium text-[#798FFF] 2xl:text-base text-sm leading-none">
|
||
{consultations}
|
||
</p>
|
||
</div>
|
||
<div className="py-[9px]">
|
||
<Slider
|
||
min={10}
|
||
max={1000}
|
||
step={10}
|
||
value={consultations}
|
||
onChange={(value) => setConsultations(value)}
|
||
tooltip={false}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<Button
|
||
icon={
|
||
isToolEnabled ? (
|
||
<CloseIcon className="2xl:w-8 w-6 2xl:h-8 h-6" />
|
||
) : (
|
||
<ArrowRightIcon className="2xl:w-8 2xl:h-8" />
|
||
)
|
||
}
|
||
color={isToolEnabled ? "secondary" : "primary"}
|
||
onClick={() => setIsToolEnabled((prev) => !prev)}
|
||
className="px-6 py-4 w-full h-fit self-center sm:flex hidden"
|
||
>
|
||
{isToolEnabled ? "Отключить" : "Включить"} инструмент
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="col-span-3 grid xl:grid-cols-2">
|
||
<div className="xl:block sm:grid grid-cols-2">
|
||
<div className="2xl:px-8 2xl:py-6 p-4 border border-[#3D425C] grid grid-cols-2 gap-4 items-center">
|
||
<div className="flex flex-col gap-4">
|
||
<p className="text-sm">Срок реализации</p>
|
||
<p
|
||
className={`2xl:text-5xl text-[32px] font-gilroy font-medium flex items-end gap-1.5 w-fit ${
|
||
isToolEnabled ? "text-gradient" : ""
|
||
}`}
|
||
>
|
||
<span className="2xl:leading-none leading-[115%]">
|
||
{isToolEnabled
|
||
? implementationPeriod
|
||
: oldImplementationPeriod}
|
||
</span>
|
||
<span className="2xl:text-2xl xl:text-xl text-base">
|
||
{isToolEnabled
|
||
? implementationPeriodEnding
|
||
: oldImplementationPeriodEnding}
|
||
</span>
|
||
</p>
|
||
</div>
|
||
<p
|
||
className={`text-xs transition-opacity ${
|
||
isToolEnabled ? `opacity-100` : `opacity-0`
|
||
}`}
|
||
>
|
||
На{" "}
|
||
<span className="text-[#798FFF]">
|
||
{diffImplementationPeriod} {diffImplementationPeriodEnding}
|
||
</span>{" "}
|
||
вы сократили
|
||
<br />
|
||
срок реализации проекта
|
||
</p>
|
||
</div>
|
||
|
||
<div className="2xl:px-8 2xl:py-6 p-4 border xl:border-t-0 sm:border-t border-t-0 xl:border-l sm:border-l-0 border-l border-[#3D425C] grid grid-cols-2 gap-4 items-center">
|
||
<div className="flex flex-col gap-4">
|
||
<p className="text-sm">Месячный доход</p>
|
||
<p
|
||
className={`2xl:text-5xl text-[32px] font-gilroy font-medium flex items-end gap-1.5 w-fit ${
|
||
isToolEnabled ? "text-gradient" : ""
|
||
}`}
|
||
>
|
||
<span className="2xl:leading-none leading-[115%]">
|
||
{isToolEnabled ? monthlyIncome : oldMonthlyIncome}
|
||
</span>
|
||
<span className="2xl:text-2xl xl:text-xl text-base">
|
||
млн руб.
|
||
</span>
|
||
</p>
|
||
</div>
|
||
|
||
<p
|
||
className={`text-xs transition-opacity ${
|
||
isToolEnabled ? `opacity-100` : `opacity-0`
|
||
}`}
|
||
>
|
||
На{" "}
|
||
<span className="text-[#798FFF]">
|
||
{diffMonthlyIncome} млн руб.
|
||
</span>{" "}
|
||
в месяц
|
||
<br />
|
||
вы заработали больше
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="2xl:px-8 2xl:py-6 sm:px-6 sm:py-4 p-4 border xl:border-l-0 xl:border-t border-t-0 border-[#3D425C] flex flex-col gap-4">
|
||
<p className="text-sm">Статистика продаж</p>
|
||
<div className="flex flex-col 2xl:gap-3 sm:gap-1.5 gap-2">
|
||
<div className="flex sm:gap-4 gap-2">
|
||
<div className="flex flex-col 2xl:gap-3 sm:gap-1.5 gap-2 justify-around">
|
||
<p className="2xl:text-sm text-xs">100%</p>
|
||
<p className="2xl:text-sm text-xs">
|
||
{isToolEnabled ? 48 : 30}%
|
||
</p>
|
||
<p className="2xl:text-sm text-xs">
|
||
{isToolEnabled ? 42 : 30}%
|
||
</p>
|
||
</div>
|
||
<div className="w-full flex flex-col 2xl:gap-3 sm:gap-1.5 gap-2 justify-around">
|
||
<div className="bg-[#212431] rounded-full flex justify-center">
|
||
<div
|
||
className={`2xl:py-3.5 py-2 rounded-full w-full transition-all ${
|
||
isToolEnabled ? "bg-gradient" : "bg-[#52587A]"
|
||
}`}
|
||
>
|
||
<p className="text-center">{consultations}</p>
|
||
</div>
|
||
</div>
|
||
<div className="bg-[#212431] rounded-full flex justify-center">
|
||
<div
|
||
className={`2xl:py-3.5 py-2 rounded-full transition-all ${
|
||
isToolEnabled
|
||
? "w-[60%] bg-gradient"
|
||
: "w-[50%] bg-[#52587A]"
|
||
}`}
|
||
>
|
||
<p className="text-center">
|
||
{isToolEnabled ? reservation : oldReservation}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="bg-[#212431] rounded-full flex justify-center">
|
||
<div
|
||
className={`2xl:py-3.5 py-2 rounded-full transition-all ${
|
||
isToolEnabled
|
||
? "w-[32%] bg-gradient"
|
||
: "w-[22%] bg-[#52587A]"
|
||
}`}
|
||
>
|
||
<p className="text-center">
|
||
{isToolEnabled ? sales : oldSales}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="flex flex-col 2xl:gap-3 sm:gap-1.5 gap-2 justify-around">
|
||
<p className="2xl:text-sm text-xs text-[#8088A7] whitespace-nowrap">
|
||
<span className="sm:inline hidden">
|
||
Консультаций в офисе
|
||
</span>
|
||
<span className="sm:hidden inline">Консультации</span>
|
||
</p>
|
||
<p className="2xl:text-sm text-xs text-[#8088A7] whitespace-nowrap">
|
||
Бронь<span className="sm:inline hidden"> квартиры</span>
|
||
</p>
|
||
<p className="2xl:text-sm text-xs text-[#8088A7] whitespace-nowrap">
|
||
Продажа
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<Button
|
||
icon={
|
||
isToolEnabled ? (
|
||
<CloseIcon className="2xl:w-8 w-6 2xl:h-8 h-6" />
|
||
) : (
|
||
<ArrowRightIcon className="2xl:w-8 2xl:h-8" />
|
||
)
|
||
}
|
||
color={isToolEnabled ? "secondary" : "primary"}
|
||
onClick={() => setIsToolEnabled((prev) => !prev)}
|
||
className="px-6 py-4 w-full h-fit self-center sm:hidden flex"
|
||
>
|
||
{isToolEnabled ? "Отключить" : "Включить"} инструмент
|
||
</Button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default Calc;
|