This commit is contained in:
ilia 2023-02-19 10:28:45 +03:00
parent f3e676e163
commit 6f345babf6
19 changed files with 887 additions and 2286 deletions

117
package-lock.json generated
View File

@ -18,12 +18,14 @@
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"antd": "^5.2.1", "antd": "^5.2.1",
"axios": "^1.3.3",
"chart.js": "^4.2.1", "chart.js": "^4.2.1",
"mapbox-gl": "^2.12.1", "mapbox-gl": "^2.12.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.8.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"redux": "^4.2.1", "redux": "^4.2.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
@ -3323,6 +3325,14 @@
} }
} }
}, },
"node_modules/@remix-run/router": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz",
"integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==",
"engines": {
"node": ">=14"
}
},
"node_modules/@rollup/plugin-babel": { "node_modules/@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@ -5162,6 +5172,29 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/axios": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.3.tgz",
"integrity": "sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/axobject-query": { "node_modules/axobject-query": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz",
@ -14298,6 +14331,11 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/psl": { "node_modules/psl": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@ -15267,6 +15305,36 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-router": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz",
"integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==",
"dependencies": {
"@remix-run/router": "1.3.2"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz",
"integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==",
"dependencies": {
"@remix-run/router": "1.3.2",
"react-router": "6.8.1"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-scripts": { "node_modules/react-scripts": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@ -20454,6 +20522,11 @@
"reselect": "^4.1.7" "reselect": "^4.1.7"
} }
}, },
"@remix-run/router": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz",
"integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA=="
},
"@rollup/plugin-babel": { "@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@ -21845,6 +21918,28 @@
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz",
"integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==" "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg=="
}, },
"axios": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.3.tgz",
"integrity": "sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
},
"dependencies": {
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
}
}
},
"axobject-query": { "axobject-query": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz",
@ -28312,6 +28407,11 @@
} }
} }
}, },
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"psl": { "psl": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@ -28975,6 +29075,23 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
}, },
"react-router": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz",
"integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==",
"requires": {
"@remix-run/router": "1.3.2"
}
},
"react-router-dom": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz",
"integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==",
"requires": {
"@remix-run/router": "1.3.2",
"react-router": "6.8.1"
}
},
"react-scripts": { "react-scripts": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",

View File

@ -13,12 +13,14 @@
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"antd": "^5.2.1", "antd": "^5.2.1",
"axios": "^1.3.3",
"chart.js": "^4.2.1", "chart.js": "^4.2.1",
"mapbox-gl": "^2.12.1", "mapbox-gl": "^2.12.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.8.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"redux": "^4.2.1", "redux": "^4.2.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",

54
public/200.html Normal file
View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<style>
*::-webkit-scrollbar{
display: none;
margin: 0;
padding: 0;
}
html, body {
margin: 0;
padding: 0;
}
</style>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
public/location_pin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

104
public/points.geojson Normal file
View File

@ -0,0 +1,104 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.08647667577779,
45.0039321850457
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.04491379939927,
45.03243514655392
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.97323072498696,
45.06542932356558
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.95386129652343,
45.10189657981991
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.97872396028441,
45.13042706066193
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.96503174898015,
45.01385308177049
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.112531237076155,
45.04610100871051
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.11927597081987,
45.0588865314364
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.94055848744463,
45.045496678942584
],
"type": "Point"
}
}
]
}

View File

@ -416,6 +416,105 @@
], ],
"type": "Polygon" "type": "Polygon"
} }
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.08647667577779,
45.0039321850457
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.04491379939927,
45.03243514655392
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.97323072498696,
45.06542932356558
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.95386129652343,
45.10189657981991
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.97872396028441,
45.13042706066193
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.96503174898015,
45.01385308177049
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.112531237076155,
45.04610100871051
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.11927597081987,
45.0588865314364
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.94055848744463,
45.045496678942584
],
"type": "Point"
}
} }
] ]
} }

View File

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import { Sidebar } from './components/Sidebar'; import { Sidebar } from './components/Sidebar';
import { TheMap } from './components/TheMap'; import { TheMap } from './components/TheMap';
import {Provider} from 'react-redux' import {Provider} from 'react-redux';
import {RouterProvider} from 'react-router-dom'
import router from './router'
import store from './store'; import store from './store';
import 'antd/dist/reset.css'; import 'antd/dist/reset.css';
@ -10,8 +12,7 @@ function App() {
return ( return (
<div className="App"> <div className="App">
<Provider store={store}> <Provider store={store}>
<TheMap /> <RouterProvider router={router}></RouterProvider>
<Sidebar />
</Provider> </Provider>
</div> </div>
); );

View File

@ -0,0 +1,62 @@
import 'chart.js/auto';
import react, {useRef} from 'react'
import { Line } from "react-chartjs-2";
interface IChart{
data: number[],
days: number[]
}
export const CardChart: react.FC<IChart> = (props) => {
const ref = useRef();
const data = {
labels: props.days,
datasets: [
{
data: props.data,
fill: true,
backgroundColor: 'rgba(75,192,192,0.2)',
borderColor: 'rgba(75,192,192,1)'
},
],
};
return <div style={{width: 340,
height: 170,}}>
<Line
data={data}
ref={ref}
style={{
width: 340,
height: 170,
transform: 'translateX(-10px)'
}}
options={{
scales: {
y: {
beginAtZero: true,
title: {
text: "руб",
display: true
}
},
x: {
title: {
text: "Дней до сдачи",
display: true
}
}
},
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: false
}
}
}
}></Line>
</div>
}

View File

@ -0,0 +1,65 @@
import react from 'react'
import {Card, Checkbox, Typography} from 'antd'
import './style.css'
import { CardChart } from '../CardChart';
interface IPrimaryCard{
prices: {
days: number,
price: number
}[],
community: string;
status: string;
risk: string;
floor: string;
area: number;
days_to_be_done: number;
developer: string;
increase_procent: string;
days_for_increase: number;
}
export const PrimaryCard: react.FC<IPrimaryCard> = (props) => {
var deal_text = (deal_type: string) => {
if (deal_type == 'bad') return {
status: 'Сделка плохая',
color: 'red'
}
if (deal_type == 'riskey') return {
status: 'Сделка рискованная',
color: 'black'
}
if (deal_type == 'good') return {
status: 'Сделка хорошая',
color: 'green'
}
}
var mapped_price = props.prices.map((e) => e.price);
var mapped_days = props.prices.map((e) => e.days);
return <Card title={props.community} style={{width: 400}}>
<div className="container">
<div className='ch-b__text'>
<Checkbox checked={props.status == 'short'}/>
<Typography.Text>Шорт</Typography.Text>
</div>
<Typography.Text>
Застрощик: <strong>{props.developer}</strong>
</Typography.Text>
<Typography.Text>
Дней до сдачи: <strong>{props.days_to_be_done}</strong>
</Typography.Text>
<Typography.Text>
Рекомендация: <strong style={{color: deal_text(props.risk)?.color as any}}>
{deal_text(props.risk)?.status}
</strong>
</Typography.Text>
<Typography.Text>Предполагаемая прибыль <strong>{props.increase_procent}%</strong></Typography.Text>
<Typography.Text>Этаж: <strong>{props.floor}</strong></Typography.Text>
<Typography.Text>Площадь: <strong>{props.area} кв.м</strong></Typography.Text>
<Typography.Text>Ждать прибыли: <strong>{props.days_for_increase} дней (дня)</strong></Typography.Text>
<CardChart data={mapped_price} days={mapped_days}/>
</div>
</Card>
}

View File

@ -0,0 +1,9 @@
.ch-b__text{
display: flex;
gap: 10px;
}
.container{
display: flex;
flex-direction: column;
gap: 5px;
}

View File

@ -0,0 +1,52 @@
import react from 'react'
import {Card, Typography} from 'antd';
import './style.css'
interface ISecondaryCard {
link: string;
floor: number;
rooms_count: number;
area: number;
year_of_construction: number;
living_meters: number;
kitchen_meters: number;
pred_price: number;
diff: number;
marker: string;
}
export const SecondaryCard: react.FC<ISecondaryCard> = (props) => {
var color = 'green';
if (props.marker == 'underpriced') color = 'red';
return <Card>
<div className='sec__container'>
<div className="stat-item">
<div className="colored" style={{backgroundColor: `${color}!important`}}></div>
<Typography.Link
href={props.link} strong
>Ссылка на квартиру
</Typography.Link>
</div>
<Typography.Text>
Этаж: <strong>{props.floor}</strong>
</Typography.Text>
<Typography.Text>
Количество комнат: <strong>{props.rooms_count}</strong>
</Typography.Text>
<Typography.Text>
Площадь: <strong>{props.area} кв.м</strong>
</Typography.Text>
<Typography.Text>
Год постройки: <strong>{props.year_of_construction} г.</strong>
</Typography.Text>
<Typography.Text>
Предсказанная цена: <strong>{props.pred_price.toFixed(0)} р</strong>
</Typography.Text>
<Typography.Text>
Процент отклонения: <strong>{(props.diff * -1).toFixed(1)}%</strong>
</Typography.Text>
</div>
</Card>
}

View File

@ -0,0 +1,16 @@
.sec__container{
display: flex;
flex-direction: column;
}
.stat-item{
display: flex;
align-items: center;
gap: 10px
}
.colored{
background-color: red;
width: 10px;
height: 10px;
border-radius: 1000px;
}

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,116 @@ import * as mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css'; import 'mapbox-gl/dist/mapbox-gl.css';
import { useDispatch } from 'react-redux/es/exports'; import { useDispatch } from 'react-redux/es/exports';
import { setActive, setName } from '../../store/slices/sidebarSlice'; import { setActive, setName } from '../../store/slices/sidebarSlice';
import './style.css'
(mapboxgl as any).accessToken = 'pk.eyJ1IjoiaWxpYXZhcyIsImEiOiJjazcwdXU0dHkwMGViM21ta3VxaHB2YWNqIn0.yHEDUiatwp4dy4MM3ywnOQ'; (mapboxgl as any).accessToken = 'pk.eyJ1IjoiaWxpYXZhcyIsImEiOiJjazcwdXU0dHkwMGViM21ta3VxaHB2YWNqIn0.yHEDUiatwp4dy4MM3ywnOQ';
const points = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.08647667577779,
45.0039321850457
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.04491379939927,
45.03243514655392
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.97323072498696,
45.06542932356558
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.95386129652343,
45.10189657981991
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.97872396028441,
45.13042706066193
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.96503174898015,
45.01385308177049
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.112531237076155,
45.04610100871051
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
39.11927597081987,
45.0588865314364
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
38.94055848744463,
45.045496678942584
],
"type": "Point"
}
}
]
}
export const TheMap: react.FC = () => { export const TheMap: react.FC = () => {
const mapContainer = useRef(null); const mapContainer = useRef(null);
const map = useRef(null as any); const map = useRef(null as any);
@ -49,6 +155,36 @@ export const TheMap: react.FC = () => {
'line-width': 2 'line-width': 2
} }
}); });
map.current.loadImage('/location_pin.png', (error: any, image: any) => {
if (error) throw error;
map.current.addImage('point_icon', image)
map.current.addSource('building_points', {
type: 'geojson',
data: '/points.geojson'
})
map.current.addLayer({
'id': 'building_points_geojson',
'type': 'symbol',
'source': 'building_points',
'layout': {
'icon-image': 'point_icon',
}
})
map.current.on('click', 'building_points_geojson', (e: any) => {
console.log(e.features[0].properties.name)
dispatch(setActive(true));
dispatch(setName(e.features[0].properties.name))
});
map.current.on('mouseenter', 'building_points_geojson', () => {
map.current.getCanvas().style.cursor = 'pointer';
});
map.current.on('mouseleave', 'building_points_geojson', () => {
map.current.getCanvas().style.cursor = '';
});
})
map.current.on('click', 'buildings-layer', (e: any) => { map.current.on('click', 'buildings-layer', (e: any) => {
console.log(e.features[0].properties.name) console.log(e.features[0].properties.name)
dispatch(setActive(true)); dispatch(setActive(true));

View File

View File

@ -0,0 +1,11 @@
import react from 'react'
import { TheMap } from '../../components/TheMap'
import { Sidebar } from '../../components/Sidebar'
export const MapPage: react.FC = () => {
return <>
<TheMap />
<Sidebar />
</>
}

View File

@ -0,0 +1,112 @@
import react from 'react'
import {Switch, Typography, Slider, Button, Divider} from 'antd';
import './style.css'
import axios from 'axios';
import { PrimaryCard } from '../../components/PrimaryCard';
import { SecondaryCard } from '../../components/SecondaryCard';
var secondary_url = 'https://dev.akarpov.ru/api/search/secondary';
var primary_url = 'https://dev.akarpov.ru/api/search/primary';
export const SearchPage: react.FC = () => {
const [searchType, setSearchType] = react.useState('perv');
const [shortLong, setShortLong] = react.useState('short');
const [minMaxPrice, setMinMaxPrice] = react.useState([0, 0]);
const [underOver, setUnderOver] = react.useState('overpriced')
const [pervData, setPervData] = react.useState([]);
const [secData, setSecData] = react.useState([]);
var maxPrices = {
'perv': {
'min': 4.1,
'max': 17.3
},
'vtor': {
'min': 1,
'max': 10
}
}
return <div className='filters__centered'>
<div className="filters">
<Typography.Title level={4}>Фильтры</Typography.Title>
<div className="perv-sec__container">
<Typography.Text style={{textAlign: 'center'}}>Первичка</Typography.Text>
<Switch onChange={(e) => {
if (e) {
setSearchType('vtor')
} else {
setSearchType('perv')
}
}}></Switch>
<Typography.Text>Вторичка</Typography.Text>
</div>
{
searchType == 'perv' ? <div className="perv-sec__container">
<Typography.Text>Краткосрочные инвестиции</Typography.Text>
<Switch onChange={(e) => {
if (e) setShortLong('long')
else setShortLong('short')
}}></Switch>
<Typography.Text>Долгосрочные инвестиции</Typography.Text>
</div> : <></>
}
{
searchType == 'vtor' ? <div className="perv-sec__container">
<Typography.Text>Недооцененные квартиры</Typography.Text>
<Switch onChange={(e) => {
if (e) setUnderOver('overpriced')
else setUnderOver('underpriced')
}}></Switch>
<Typography.Text>Переоцененные квартиры</Typography.Text>
</div> : <></>
}
<div>
<Typography.Text>Фильтр цены (млн. руб)</Typography.Text>
<Slider
range
min={((maxPrices as any)[searchType] as any)['min']}
max={((maxPrices as any)[searchType] as any)['max']}
step={0.1}
onChange={(e) => {
setMinMaxPrice(e)
}}
></Slider>
</div>
<Button
type='primary'
onClick={async () => {
var origin = secondary_url
if (searchType == 'perv') {
origin = primary_url;
origin += `?status=${shortLong}&price_max=${minMaxPrice[1] * 1000000}&price_min=${minMaxPrice[0] * 1000000}`
const answer = await axios.get(origin);
console.log(answer)
setPervData(answer.data.reverse());
setSecData([]);
} else {
origin += `?price_max=${minMaxPrice[1] * 1000000}&price_min=${minMaxPrice[0] * 1000000}&marker=${underOver}`
const answer = await axios.get(origin);
setSecData(answer.data);
setPervData([]);
}
}}
>Найти</Button>
</div>
<Divider></Divider>
<div className="content">
{pervData.map((e) => {
return <PrimaryCard {...e as any}/>
})}
{
secData.map((e) => {
return <SecondaryCard {...e as any}/>
})
}
</div>
</div>
}

View File

@ -0,0 +1,27 @@
.perv-sec__container{
display: flex;
gap: 10px;
}
.filters{
width: 500px;
display: flex;
flex-direction: column;
gap: 10px;
padding: 20px;
}
.filters__centered{
justify-content: center;
display: flex;
flex-direction: column;
}
.content{
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 30px;
padding: 20px;
justify-content: space-between;
}

16
src/router.tsx Normal file
View File

@ -0,0 +1,16 @@
import { createBrowserRouter } from "react-router-dom";
import { MapPage } from "./pages/MapPage";
import { SearchPage } from "./pages/SearchPage";
const router = createBrowserRouter([
{
'path': '/search',
'element': <SearchPage />
},
{
'path': '/map',
'element': <MapPage />
}
])
export default router;