legacy/pairent_frontend_react/src/pages/IndexPage/index.jsx

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>
);
}
}