diff --git a/pairent_frontend_react/src/API/ApartamentService.js b/pairent_frontend_react/src/API/ApartamentService.js new file mode 100644 index 0000000..acfbb63 --- /dev/null +++ b/pairent_frontend_react/src/API/ApartamentService.js @@ -0,0 +1,18 @@ +import axios from 'axios'; + +export default class ApartamentService { + static async getAll(limit, offest) { + const response = await axios.get('http://127.0.0.1:8000/api/apartaments/', { + params: { + limit: limit, + ...(offest !== 0 ? {offset: offest} : {}) + } + }) + return response; + } + + static async getById(id) { + const response = await axios.get('http://127.0.0.1:8000/api/apartament/' + id) + return response; + } +} \ No newline at end of file diff --git a/pairent_frontend_react/src/App.css b/pairent_frontend_react/src/App.css index 9f3fd83..e4c12d3 100644 --- a/pairent_frontend_react/src/App.css +++ b/pairent_frontend_react/src/App.css @@ -24,3 +24,59 @@ main{ height: 1px; background-color: #CCC; } + +/*Кнопки выбора страницы*/ +.btnSection{ + display: flex; + margin-top: 24px; +} + +/*Кнопка "Показать весь список*/ +.allBtn{ + width: 208px; + height: 32px; + background-color: #fff; + border: 1px solid #ccc; + border-radius: 12px; + font-weight: 500; + font-size: 14px; +} + +/*Кнопки переключения страниц*/ +.choiceBtn{ + display: flex; + margin-left: auto; + margin-right: auto; +} + +/*Кнопки "предыдущая" и "следующая"*/ +.btnPrevious, .btnNext { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 20px; + background: #fff; + box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.25); +} + +.btnNext{ + margin-left: 10px; +} + +.pageBtn{ + width: 36px; + height: 36px; + margin-left: 10px; + background: #D9D9D9; + border-radius: 12px; + font-weight: 700; + font-size: 16px; +} + +.activePage{ + border: 2px solid #175FA9; + background-color: #fff; + color: #175FA9; +} \ No newline at end of file diff --git a/pairent_frontend_react/src/components/CardSmallApartament/CardSmallApartament.jsx b/pairent_frontend_react/src/components/CardSmallApartament/CardSmallApartament.jsx new file mode 100644 index 0000000..d6aed4f --- /dev/null +++ b/pairent_frontend_react/src/components/CardSmallApartament/CardSmallApartament.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import './styles/CardSmallApartament.css'; + +const CardSmallApartament = function (props) { + return ( +
+ +

{props.results.perimetrs} м², {props.results.rooms}-х комнатная

+

{props.results.price}₽ / мес.

+

{props.results.address}

+
+ ); +} + +export default CardSmallApartament; \ No newline at end of file diff --git a/pairent_frontend_react/src/components/CardSmallApartament/styles/CardSmallApartament.css b/pairent_frontend_react/src/components/CardSmallApartament/styles/CardSmallApartament.css new file mode 100644 index 0000000..e85c458 --- /dev/null +++ b/pairent_frontend_react/src/components/CardSmallApartament/styles/CardSmallApartament.css @@ -0,0 +1,29 @@ +/*Квартира*/ +.viewedElem{ + width: 24%; + padding-bottom: 1%; + background: #fff; + border: 2px solid #ccc; + border-radius: 20px; +} + +.viewedElem img{ + width: 100%; + margin-bottom: 3%; +} + +.viewedElem h3, .viewedElem p{ + margin-left: 5%; +} + +.viewedElem h3{ + font-weight: 600; + font-size: 24px; + color: #222; +} + +.viewedElem p{ + font-weight: 500; + font-size: 16px; + color: #222; +} \ No newline at end of file diff --git a/pairent_frontend_react/src/components/Favorites/Favorites.jsx b/pairent_frontend_react/src/components/Favorites/Favorites.jsx index 6b40f60..a2f35f0 100644 --- a/pairent_frontend_react/src/components/Favorites/Favorites.jsx +++ b/pairent_frontend_react/src/components/Favorites/Favorites.jsx @@ -1,29 +1,31 @@ -import React, { useEffect, useState } from 'react'; -import CardApartament from '../../components/CardApartament/CardApartament'; -import axios from 'axios'; /* БЛЯ 10 КБ СУКА */ +import React from 'react'; + +import FavoritesApartamentsList from './FavoritesApartamentsList'; +import LastView from '../../components/LastView/LastView'; + import './styles/Favorites.css'; const Favorites = function () { - const [apartaments, setApartaments] = useState([]) - - async function fetchApartaments() { - const response = await axios.get('http://127.0.0.1:8000/api/apartaments/') - setApartaments(response.data.results) - } - - useEffect(() => { - fetchApartaments() - }) return( <>

Избранное

- {apartaments.map((apartament, index) => - <> - -
- - )} + +
+ + + +
+ ); } diff --git a/pairent_frontend_react/src/components/Favorites/FavoritesApartamentsList.jsx b/pairent_frontend_react/src/components/Favorites/FavoritesApartamentsList.jsx new file mode 100644 index 0000000..91277a1 --- /dev/null +++ b/pairent_frontend_react/src/components/Favorites/FavoritesApartamentsList.jsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from 'react'; +import ApartamentService from '../../API/ApartamentService'; +import {useFetching} from '../../hooks/useFetching'; +import {getPageCount} from '../../utils/Pages'; + +import CardApartament from '../../components/CardApartament/CardApartament'; +import Pagination from '../../components/UI/Pagination/Pagination'; + +const FavoritesApartamentsList = () => { + const [apartaments, setApartaments] = useState([]) + const [totalPages, setTotalPages] = useState(0); + const limit = 1; + const [page, setPage] = useState(1); + + const [fetchApartaments, isApartamentsLoading, apartamentsError] = useFetching(async (limit) => { + let offset = (page - 1) * limit; + const response = await ApartamentService.getAll(limit, offset); + setApartaments(response.data.results) + const totalCount = response.data.count + setTotalPages(getPageCount(totalCount, limit)) + }) + + useEffect(() => { + fetchApartaments(limit) + }, [page]) + + const changePage = (page) => { + setPage(page) + } + + // TODO: Переделать, сейчас сделанно для MVP (то есть заглушки) + if (!apartaments.length && !isApartamentsLoading && !apartamentsError) { + return ( +

В избранном ничего нет :/

+ ) + } + + if (apartamentsError){ + return( +

Произошла ошибка ({apartamentsError})

+ ) + } + + return ( + <> + {isApartamentsLoading && +

Идет загрузка...

+ } + {apartaments.map((apartament, index) => + <> + +
+ + )} + + + ); +}; + +export default FavoritesApartamentsList; \ No newline at end of file diff --git a/pairent_frontend_react/src/components/Favorites/styles/Favorites.css b/pairent_frontend_react/src/components/Favorites/styles/Favorites.css index d4ee7be..df20b4f 100644 --- a/pairent_frontend_react/src/components/Favorites/styles/Favorites.css +++ b/pairent_frontend_react/src/components/Favorites/styles/Favorites.css @@ -14,3 +14,54 @@ h2{ margin-left: 34%; border: 1px solid #ccc; } + +/*Кнопки с вопросами*/ +.questionBtnSection{ + display: flex; + justify-content: space-between; + margin-top: 55px; +} + +.questionBtn{ + width: 323px; + height: 128px; + padding: 2% 5% 2% 3%; + border-radius: 12px; + text-align: left; +} + +.questionBtn h3{ + font-size: 18px; + color: #fff; +} + +.questionBtn p{ + margin-top: 16px; + font-size: 16px; + line-height: 19px; + color: #fff; +} + +.orangeBtn{ + background: linear-gradient(180deg, #F76D09 0%, #FFA800 100%); +} + +.orangeBtn:active{ + background: linear-gradient(180deg, #FFA800 0%, #F76D09 100%); +} + +.greenBtn{ + background: linear-gradient(180deg, #107A34 0%, #51A633 100%); +} + +.greenBtn:active{ + background: linear-gradient(180deg, #51A633 0%, #107A34 100%); +} + +.blueBtn{ + background: linear-gradient(180deg, #2E50A7 0%, #0993F7 100%); +} + +.blueBtn:active{ + background: linear-gradient(180deg, #0993F7 0%, #2E50A7 100%); +} \ No newline at end of file diff --git a/pairent_frontend_react/src/components/LastView/LastView.jsx b/pairent_frontend_react/src/components/LastView/LastView.jsx new file mode 100644 index 0000000..3ea6935 --- /dev/null +++ b/pairent_frontend_react/src/components/LastView/LastView.jsx @@ -0,0 +1,51 @@ +import React, { useEffect, useState } from 'react'; +import CardSmallApartament from '../../components/CardSmallApartament/CardSmallApartament'; +import axios from 'axios'; +import './styles/LastView.css'; + +const LastView = function () { + const [apartamentsLastView, setApartamentsLastView] = useState([]) + + async function fetchLastApartamentsView() { + const response = await axios.get('http://127.0.0.1:8000/api/apartaments/') + setApartamentsLastView(response.data.results) + } + + useEffect(() => { + fetchLastApartamentsView() + }, []) + + return( + <> +
+
+

Недавно просмотренные 15

+
+ +
+ + +
+
+
+
+ {apartamentsLastView.map((apartament, index) => + <> + + + )} +
+
+ + ); +} + +export default LastView; \ No newline at end of file diff --git a/pairent_frontend_react/src/components/LastView/styles/LastView.css b/pairent_frontend_react/src/components/LastView/styles/LastView.css new file mode 100644 index 0000000..65d1af6 --- /dev/null +++ b/pairent_frontend_react/src/components/LastView/styles/LastView.css @@ -0,0 +1,53 @@ +/*Недавно просмотренные*/ +.viewedSection{ + margin-top: 46px; +} + +.viewedTittle{ + display: flex; + justify-content: space-between; +} + +.viewedTittle h2{ + font-weight: 700; + font-size: 20px; + color: #222; +} + +.viewedTittle span{ + margin-left: 30px; + font-size: 16px; + color: #A9A9A9; +} + +/*Кнопки*/ +.viewedBtnSection{ + display: flex; +} + +/*Кнопка "посмотреть все недавние"*/ +.btnViewed{ + width: 208px; + height: 32px; + margin-top: 15px; + margin-right: 22px; + background: #fff; + border: 1px solid #CCCCCC; + border-radius: 12px; + font-size: 14px; + font-weight: 500; + color: #ccc; +} + +/*Кнопки "предыдущие" и "следующие"*/ +.viewedBtn{ + display: flex; + align-items: center; +} + +/*Недавно просмотренные квартиры*/ +.viewedBlock{ + display: flex; + justify-content: space-between; + margin-top: 27px; +} \ No newline at end of file diff --git a/pairent_frontend_react/src/components/UI/Pagination/Pagination.jsx b/pairent_frontend_react/src/components/UI/Pagination/Pagination.jsx new file mode 100644 index 0000000..b6acba2 --- /dev/null +++ b/pairent_frontend_react/src/components/UI/Pagination/Pagination.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import {getPagesArray} from "../../../utils/Pages"; + +const Pagination = ({totalPages, page, changePage}) => { + let pagesArray = getPagesArray(totalPages); + let previousPage = page !== 1 ? page - 1 : page + let nextPage = page < pagesArray.length ? page + 1 : page + + return ( +
+ +
+ + {pagesArray.map(p => + + )} + +
+
+ ); +}; + +export default Pagination; \ No newline at end of file diff --git a/pairent_frontend_react/src/components/UI/Pagination/styles/Pagination.css b/pairent_frontend_react/src/components/UI/Pagination/styles/Pagination.css new file mode 100644 index 0000000..e69de29 diff --git a/pairent_frontend_react/src/hooks/useFetching.js b/pairent_frontend_react/src/hooks/useFetching.js new file mode 100644 index 0000000..44c39b2 --- /dev/null +++ b/pairent_frontend_react/src/hooks/useFetching.js @@ -0,0 +1,19 @@ +import {useState} from "react"; + +export const useFetching = (callback) => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + + const fetching = async (...args) => { + try { + setIsLoading(true) + await callback(...args) + } catch (e) { + setError(e.message); + } finally { + setIsLoading(false) + } + } + + return [fetching, isLoading, error] +} \ No newline at end of file diff --git a/pairent_frontend_react/src/utils/Pages.js b/pairent_frontend_react/src/utils/Pages.js new file mode 100644 index 0000000..f774d0d --- /dev/null +++ b/pairent_frontend_react/src/utils/Pages.js @@ -0,0 +1,11 @@ +export const getPageCount = (totalCount, limit) => { + return Math.ceil(totalCount / limit) +} + +export const getPagesArray = (totalPages) => { + let result = []; + for (let i = 0; i < totalPages; i++) { + result.push(i + 1) + } + return result; +} \ No newline at end of file