418 lines
12 KiB
JavaScript
418 lines
12 KiB
JavaScript
import React, { useState } from "react";
|
|
import ISVGIcon from '../../components/UI/Icon/SVGIcon';
|
|
import ApartmentsList from "../../components/ApartmentsList";
|
|
import { Filters } from "../../API/Filters";
|
|
|
|
import styled from 'styled-components';
|
|
|
|
// swiper customization
|
|
import './swiper.css';
|
|
import ApartamentService from "../../API/ApartamentService";
|
|
import { HashLoader } from 'react-spinners';
|
|
|
|
import AwesomeSlider from 'react-awesome-slider';
|
|
import 'react-awesome-slider/dist/styles.css';
|
|
|
|
import constants from "../../constants";
|
|
|
|
import Pagination from '../../components/UI/Pagination';
|
|
|
|
const API_ROOT = constants.API_ROOT;
|
|
|
|
const Select = styled.select`
|
|
padding: 6px 10px;
|
|
border: 1px solid #c2c4c2;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 2px #00000010
|
|
`;
|
|
|
|
const SVGIcon = styled(ISVGIcon)`
|
|
margin-left:0;
|
|
`;
|
|
|
|
const Title = styled.p`
|
|
font-size: 20pt;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
margin: 32px 0;
|
|
text-shadow: 0 2px 1px #00000020;
|
|
`;
|
|
|
|
const FiltersEl = styled.div`
|
|
background: url(/images/filter.png);
|
|
height: 300px;
|
|
width: 1150px;
|
|
margin: 0 auto;
|
|
margin-bottom: 60px;
|
|
border-radius: 20px;
|
|
box-shadow: 0 2px 12px #00000060;
|
|
padding: 30px;
|
|
text-shadow: 0 2px 16px #ffffffa0;
|
|
`;
|
|
|
|
const PeriodFilter = styled.div`
|
|
display: block;
|
|
|
|
margin: 12px 0;
|
|
`;
|
|
|
|
const PeriodButton = styled.button`
|
|
width: fit-content;
|
|
height: 27px;
|
|
display: inline-block;
|
|
margin-right: 16px;
|
|
|
|
border-radius: 10px;
|
|
padding: 0 16px;
|
|
box-shadow: 0 2px 16px #ffffff30;
|
|
|
|
color: ${props => props.active ? 'green' : 'black'};
|
|
box-sizing: border-box;
|
|
${props => props.active ? 'font-weight: 600;' : ''}
|
|
`;
|
|
|
|
const FilterText = styled.h1`
|
|
font-size: 16pt;
|
|
color: #f0f4f0;
|
|
user-select: none;
|
|
|
|
padding-bottom: 6px;
|
|
border-bottom: 1px solid #f0f2f060;
|
|
margin-bottom: 20px; padding-bottom: 1px
|
|
`;
|
|
|
|
const ClearButton = styled(PeriodButton)`
|
|
float: right;
|
|
color: darkred;
|
|
margin-right: 0;
|
|
`;
|
|
|
|
|
|
const SubmitBtn = styled.button`
|
|
background: royalblue;
|
|
padding: 10px;
|
|
border-radius: 8px;
|
|
color: #f9f9f9;
|
|
width: 180px;
|
|
|
|
&:disabled {
|
|
background: #3a5dc8;
|
|
}
|
|
`;
|
|
|
|
const SearchBarFilter = styled.div`
|
|
background: #f2f3f2;
|
|
border-radius: 12px;
|
|
width: 100%;
|
|
height: 56px;
|
|
margin: 12px 0;
|
|
font-weight: 400;
|
|
padding: 0 16px
|
|
`;
|
|
|
|
const SearchBarText = styled.div`
|
|
display: flex;
|
|
align-items: center
|
|
`;
|
|
|
|
const SearchBarSpacer = styled.div`
|
|
height: 55px;
|
|
width: 1px;
|
|
border-left: 1px solid #c1c3c1;
|
|
display: inline-block;
|
|
margin: 0 20px
|
|
`;
|
|
|
|
const SearchBarInput = styled.input`
|
|
border: 0; background: 0;
|
|
margin: 0 16px;
|
|
margin-right: 0;
|
|
font-size: 105%;
|
|
width: 30px;
|
|
outline: none;
|
|
`;
|
|
|
|
const IndexPageRoot = styled.div`
|
|
width: 1150px;
|
|
margin: 50px auto
|
|
`;
|
|
|
|
const Splash = styled.h3`
|
|
font-weight: 500;
|
|
margin: 48px 0;
|
|
text-align: center;
|
|
`;
|
|
|
|
const SliderContainer = styled.div`
|
|
filter: drop-shadow(0 2px 8px #00000060);
|
|
clip-path: inset(0% 0% 0% 0% round 16px);
|
|
|
|
& .awssld, .awssld__container {
|
|
width: 1150px;
|
|
height: 200px;
|
|
}
|
|
`;
|
|
|
|
const FiltersForm = (props) => {
|
|
|
|
const apart_sizes = [
|
|
{ value: 0, label: "Гостинка" },
|
|
{ value: 1, label: '1 комната' },
|
|
{ value: 2, label: '2 комнаты' },
|
|
{ value: 3, label: '3 комнаты' },
|
|
{ value: 4, label: '4 комнаты' },
|
|
{ value: -1, label: 'Выберите' }
|
|
];
|
|
|
|
const def_form = {
|
|
per_day: false,
|
|
per_month: true,
|
|
|
|
area_from: '',
|
|
area_to: '',
|
|
|
|
price_from: '',
|
|
price_to: '',
|
|
|
|
address: '',
|
|
|
|
rooms: apart_sizes[5],
|
|
|
|
form_err: false
|
|
};
|
|
|
|
let cached_filters = window.sessionStorage.getItem('pairent_filters');
|
|
if (cached_filters) cached_filters = JSON.parse(cached_filters);
|
|
|
|
const [ state, setState_ ] = useState(cached_filters || def_form);
|
|
|
|
const setState = (new_state) => {
|
|
|
|
window.sessionStorage.setItem('pairent_filters', JSON.stringify({ ...state, ...new_state }));
|
|
|
|
return setState_({...state, ...new_state});
|
|
}
|
|
|
|
const onSumbit = () => {
|
|
let transformed = new Filters();
|
|
|
|
transformed.address = state.address;
|
|
transformed.area_range = {
|
|
from: state.area_from,
|
|
to: state.area_to
|
|
};
|
|
transformed.price_range = {
|
|
from: state.price_from,
|
|
to: state.price_to
|
|
};
|
|
|
|
if (state.per_day) transformed.lease_period.push('day');
|
|
if (state.per_month) transformed.lease_period.push('month');
|
|
if (state.rooms.value != -1) transformed.rooms = state.rooms.value;
|
|
|
|
if (props.onSubmit) props.onSubmit(transformed);
|
|
}
|
|
|
|
return (
|
|
<FiltersEl>
|
|
<FilterText>Фильтры</FilterText>
|
|
|
|
<PeriodFilter>
|
|
<PeriodButton
|
|
active={state.per_day ? 1 : 0}
|
|
onClick={() => setState({per_day: !state.per_day})}>
|
|
<SVGIcon src='/images/icons/calendar-day.svg' width='14' height='14' />
|
|
Посуточно
|
|
</PeriodButton>
|
|
<PeriodButton
|
|
active={state.per_month ? 1 : 0}
|
|
onClick={() => setState({per_month: !state.per_month})}>
|
|
<SVGIcon src='/images/icons/calendar.svg' width='14' height='14' />
|
|
Ежемесячно
|
|
</PeriodButton>
|
|
<ClearButton onClick={() => setState(def_form)}>
|
|
<SVGIcon src='/images/icons/eraser-fill.svg' width='14' height='14' />
|
|
Сбросить
|
|
</ClearButton>
|
|
</PeriodFilter>
|
|
|
|
<SearchBarFilter>
|
|
<SearchBarText>
|
|
Площадь
|
|
<span style={{color:'gray', paddingLeft: 10}}> от </span>
|
|
<SearchBarInput
|
|
type='text'
|
|
placeholder='—'
|
|
maxLength='3'
|
|
value={state.area_from}
|
|
onChange={(e) => setState({area_from: e.target.value})}/>
|
|
|
|
<span style={{color:'gray', paddingLeft: 10}}> до </span>
|
|
<SearchBarInput
|
|
type='text'
|
|
placeholder='—'
|
|
maxLength='3'
|
|
value={state.area_to}
|
|
onChange={(e) => setState({area_to: e.target.value})}/>
|
|
|
|
м²
|
|
|
|
<SearchBarSpacer />
|
|
|
|
<Select
|
|
onChange={e => setState({rooms: apart_sizes[e.target.value] ?? apart_sizes[5]})}
|
|
value={state.rooms.value}
|
|
>
|
|
|
|
{
|
|
apart_sizes.map((size, i) => {
|
|
return <option value={size.value} label={size.label} key={i} />;
|
|
})
|
|
}
|
|
</Select>
|
|
|
|
<SearchBarSpacer />
|
|
|
|
Цена
|
|
|
|
<span style={{color:'gray', paddingLeft: 6}}> от </span>
|
|
<SearchBarInput
|
|
type='text'
|
|
placeholder='—'
|
|
value={state.price_from}
|
|
onChange={(e) => setState({price_from: e.target.value})}
|
|
style={{width:60}} />
|
|
|
|
<span style={{color:'gray', paddingLeft: 6}}> до </span>
|
|
<SearchBarInput
|
|
type='text'
|
|
placeholder='—'
|
|
value={state.price_to}
|
|
onChange={(e) => setState({price_to: e.target.value})}
|
|
style={{width:60}} />
|
|
|
|
₽
|
|
|
|
<SearchBarSpacer />
|
|
|
|
<SearchBarInput
|
|
type='text'
|
|
placeholder='Район, квартал, ж/д, индекс...'
|
|
value={state.address}
|
|
onChange={(e) => setState({address: e.target.value})}
|
|
style={{width:300,marginLeft:0}} />
|
|
</SearchBarText>
|
|
</SearchBarFilter>
|
|
|
|
<div style={{float: 'right'}}>
|
|
<SubmitBtn onClick={onSumbit} disabled={props.loading}>
|
|
{
|
|
props.loading ?
|
|
<>
|
|
<HashLoader color='#a1adb3' cssOverride={{marginRight: 6, transform: 'translate(-3px, 2px) scale(1.3)'}} size={14} />
|
|
Загрузка данных...
|
|
</> :
|
|
<>
|
|
<SVGIcon src='/images/icons/search.svg' width='14' height='14' />
|
|
Показать варианты
|
|
</>
|
|
}
|
|
</SubmitBtn>
|
|
</div>
|
|
</FiltersEl>
|
|
);
|
|
}
|
|
|
|
export default class IndexPage extends React.Component {
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
apartments: [],
|
|
pageSize: 10,
|
|
page: 1,
|
|
data_loaded: false,
|
|
load_err: false,
|
|
loading_filters: false
|
|
};
|
|
|
|
ApartamentService.getAll(100).then(data => {
|
|
this.setState({apartments: data.data.results, data_loaded: true});
|
|
}).catch(err => {
|
|
this.setState({data_loaded: true, apartments: [], load_err: err.message});
|
|
});
|
|
|
|
this.filterData = this.filterData.bind(this);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {Filters} filters
|
|
*/
|
|
async filterData(filters) {
|
|
|
|
this.setState({loading_filters: true});
|
|
|
|
const filtered_raw = await fetch(API_ROOT + '/api/apartaments/filters/', {
|
|
method: 'POST',
|
|
body: JSON.stringify(filters),
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).catch(err => console.error(err));
|
|
|
|
const filtered = await filtered_raw.json();
|
|
|
|
this.setState({apartments: filtered, data_loaded: true, loading_filters: false, load_err: false});
|
|
}
|
|
|
|
render() {
|
|
let { page, pageSize } = this.state;
|
|
let pages = Math.floor(this.state.apartments.length / pageSize);
|
|
|
|
let current_data = this.state.apartments.slice((page * pageSize), (page * pageSize) + pageSize);
|
|
|
|
return (
|
|
<IndexPageRoot>
|
|
<SliderContainer>
|
|
<AwesomeSlider bullets={false}>
|
|
<div data-src='/images/house-s.jpg' />
|
|
<div data-src='/images/house-s-2.jpg' />
|
|
</AwesomeSlider>
|
|
</SliderContainer>
|
|
|
|
<Title>
|
|
Выбор квартиры во
|
|
<span style={{color: '#0066ff'}}> Владивостоке</span>
|
|
</Title>
|
|
|
|
<FiltersForm onSubmit={this.filterData} loading={this.state.loading_filters} />
|
|
|
|
<ApartmentsList list={current_data} loading={this.state.loading_filters} />
|
|
|
|
{
|
|
!this.state.data_loaded ?
|
|
<>
|
|
<Splash>Данные загружаются, подождите немного</Splash>
|
|
<div style={{margin: '0 auto', width: 'fit-content'}}><HashLoader color='#0077aa' /></div>
|
|
|
|
</> :
|
|
null
|
|
}
|
|
|
|
{
|
|
this.state.load_err ?
|
|
<Splash>Ошибка загрузки данных: <br/> {this.state.load_err}</Splash> :
|
|
null
|
|
}
|
|
|
|
<Pagination
|
|
pages={pages}
|
|
onChange={(page) => this.setState({ page })}
|
|
value={page}
|
|
/>
|
|
</IndexPageRoot>
|
|
);
|
|
}
|
|
} |