mirror of
https://github.com/danilt2000/Alma-vid.git
synced 2026-02-04 01:34:13 +02:00
Add expandable address to Object component
Introduced address truncation and expand/collapse functionality in Object.jsx and updated related styles in Object.scss. Improved responsive layout and fallback data in SliderObjects.jsx. Removed unused office address from contacts.js and updated Office page to use main address.
This commit is contained in:
@@ -1,13 +1,52 @@
|
|||||||
import './Object.scss';
|
import "./Object.scss";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
function Object(props) {
|
function Object(props) {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
|
const addressLimit = 30;
|
||||||
|
const isAddressTooLong = props.address && props.address.length > addressLimit;
|
||||||
|
const needsExpansion = isAddressTooLong;
|
||||||
|
|
||||||
|
const truncateAddress = (text) => {
|
||||||
|
if (!text) return "";
|
||||||
|
if (isExpanded) return text;
|
||||||
|
if (text.length <= addressLimit) return text;
|
||||||
|
|
||||||
|
const truncated = text.substring(0, addressLimit);
|
||||||
|
const lastSpaceIndex = truncated.lastIndexOf(" ");
|
||||||
|
if (lastSpaceIndex > 0) {
|
||||||
|
return truncated.substring(0, lastSpaceIndex) + "...";
|
||||||
|
}
|
||||||
|
return truncated + "...";
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleExpansion = () => {
|
||||||
|
setIsExpanded(!isExpanded);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="item">
|
<div className={`item ${isExpanded ? "item--expanded" : ""}`}>
|
||||||
<img className="item__image" src={props.image} alt="квартира" />
|
<img className="item__image" src={props.image} alt="квартира" />
|
||||||
<div className="item__info font-inter-bold">
|
<div className="item__info font-inter-bold">
|
||||||
|
<div className="item__content">
|
||||||
<p className="item__price">{props.price}</p>
|
<p className="item__price">{props.price}</p>
|
||||||
<p className="item__desc">{props.desc}</p>
|
<p className="item__desc" title={props.desc}>
|
||||||
<p className="item__address font-inter-regular">{props.address}</p>
|
{props.desc}
|
||||||
|
</p>
|
||||||
|
<p className="item__address font-inter-regular" title={props.address}>
|
||||||
|
{truncateAddress(props.address)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{needsExpansion && (
|
||||||
|
<button
|
||||||
|
className="item__expand-btn"
|
||||||
|
onClick={toggleExpansion}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{isExpanded ? "Скрыть" : "Показать больше"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../styles/vars.scss';
|
@import "../../styles/vars.scss";
|
||||||
|
|
||||||
.item__info p {
|
.item__info p {
|
||||||
margin: 0 0 12px 0;
|
margin: 0 0 12px 0;
|
||||||
@@ -7,58 +7,115 @@
|
|||||||
.item {
|
.item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
// width: 381px;
|
|
||||||
// height: 381px;
|
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
min-height: 450px;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
height: auto;
|
||||||
|
min-height: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1800px) {
|
||||||
|
min-height: 480px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 480px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1300px) {
|
@media (max-width: 1300px) {
|
||||||
height: 381px;
|
min-height: 430px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 430px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
height: 361px;
|
min-height: 410px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 410px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
height: 341px;
|
min-height: 390px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 390px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $laptopWidth) {
|
@media (max-width: $laptopWidth) {
|
||||||
height: 321px;
|
min-height: 370px;
|
||||||
margin: 0 15px;
|
margin: 0 15px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 370px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $tabletWidth) {
|
@media (max-width: $tabletWidth) {
|
||||||
height: 505px;
|
min-height: 500px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 740px) {
|
@media (max-width: 740px) {
|
||||||
height: 473px;
|
min-height: 480px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 480px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 690px) {
|
@media (max-width: 690px) {
|
||||||
height: 435px;
|
min-height: 460px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 460px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 635px) {
|
@media (max-width: 635px) {
|
||||||
height: 400px;
|
min-height: 440px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 440px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 570px) {
|
@media (max-width: 570px) {
|
||||||
height: 361px;
|
min-height: 420px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 420px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $mobileWidth) {
|
@media (max-width: $mobileWidth) {
|
||||||
height: 381px;
|
min-height: 400px;
|
||||||
|
margin: 0 10px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 420px) {
|
@media (max-width: 420px) {
|
||||||
height: 361px;
|
min-height: 380px;
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
min-height: 380px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item__image:hover {
|
.item__image:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@@ -118,7 +175,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item__info {
|
.item__info {
|
||||||
margin: 25px 0 13px 25px;
|
margin: 20px 20px 15px 20px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
@media (max-width: $mobileWidth) {
|
||||||
|
margin: 15px 15px 10px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item__content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item__price {
|
.item__price {
|
||||||
@@ -135,6 +207,10 @@
|
|||||||
|
|
||||||
.item__desc {
|
.item__desc {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 8px !important;
|
||||||
|
word-wrap: break-word;
|
||||||
|
hyphens: auto;
|
||||||
|
|
||||||
@media (min-width: 1800px) {
|
@media (min-width: 1800px) {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@@ -147,6 +223,9 @@
|
|||||||
|
|
||||||
.item__address {
|
.item__address {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
line-height: 1.3;
|
||||||
|
word-wrap: break-word;
|
||||||
|
hyphens: auto;
|
||||||
|
|
||||||
@media (min-width: 1800px) {
|
@media (min-width: 1800px) {
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
@@ -156,3 +235,47 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item__expand-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #007bff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 0;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
text-align: left;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
font-family: inherit;
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #0056b3;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1800px) {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768.98px) and (max-width: $laptopWidth) {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobileWidth) {
|
||||||
|
font-size: 11px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import Slider from 'react-slick';
|
import Slider from "react-slick";
|
||||||
import { Link } from 'react-router-dom';
|
import { useMediaQuery } from "react-responsive";
|
||||||
import { useMediaQuery } from 'react-responsive';
|
|
||||||
|
|
||||||
import 'slick-carousel/slick/slick.css';
|
import "slick-carousel/slick/slick.css";
|
||||||
import 'slick-carousel/slick/slick-theme.css';
|
import "slick-carousel/slick/slick-theme.css";
|
||||||
import './SliderObjects.scss';
|
import "./SliderObjects.scss";
|
||||||
import { Object } from '../Object/Object';
|
import { Object } from "../Object/Object";
|
||||||
import { API_CONFIG } from '../../config/contacts';
|
import { API_CONFIG } from "../../config/contacts";
|
||||||
|
|
||||||
import objectPicOne from '../../assets/images/apartaments/image-44.jpg';
|
import objectPicOne from "../../assets/images/apartaments/image-44.jpg";
|
||||||
import objectPicTwo from '../../assets/images/apartaments/image-45.jpg';
|
import objectPicTwo from "../../assets/images/apartaments/image-45.jpg";
|
||||||
import objectPicThree from '../../assets/images/apartaments/image-46.jpg';
|
import objectPicThree from "../../assets/images/apartaments/image-46.jpg";
|
||||||
|
|
||||||
const NextArrow = ({ onClick }) => {
|
const NextArrow = ({ onClick }) => {
|
||||||
return (
|
return (
|
||||||
<div className="slider-objects__arrow slider-objects__arrow_next" onClick={onClick}></div>
|
<div
|
||||||
|
className="slider-objects__arrow slider-objects__arrow_next"
|
||||||
|
onClick={onClick}
|
||||||
|
></div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PrevArrow = ({ onClick }) => {
|
const PrevArrow = ({ onClick }) => {
|
||||||
return (
|
return (
|
||||||
<div className="slider-objects__arrow slider-objects__arrow_prev" onClick={onClick}></div>
|
<div
|
||||||
|
className="slider-objects__arrow slider-objects__arrow_prev"
|
||||||
|
onClick={onClick}
|
||||||
|
></div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,20 +40,23 @@ const SliderComponent = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchObjects = async () => {
|
const fetchObjects = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(API_CONFIG.getFullURL(API_CONFIG.endpoints.rental), {
|
const response = await fetch(
|
||||||
method: 'GET',
|
API_CONFIG.getFullURL(API_CONFIG.endpoints.rental),
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'accept': '*/*'
|
accept: "*/*",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setObjects(data);
|
setObjects(data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка при загрузке объектов:', error);
|
console.error("Ошибка при загрузке объектов:", error);
|
||||||
// В случае ошибки оставляем пустой массив, компонент не сломается
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -58,8 +66,8 @@ const SliderComponent = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
dots: objects.length > (isMobileResolution ? 1 : 3), // Показываем точки если объектов больше чем влезает
|
dots: objects.length > (isMobileResolution ? 1 : 3),
|
||||||
infinite: objects.length > (isMobileResolution ? 1 : 3), // Бесконечная прокрутка только если объектов достаточно
|
infinite: objects.length > (isMobileResolution ? 1 : 3),
|
||||||
speed: 500,
|
speed: 500,
|
||||||
slidesToShow: isMobileResolution ? 1 : 3,
|
slidesToShow: isMobileResolution ? 1 : 3,
|
||||||
slidesToScroll: isMobileResolution ? 1 : 3,
|
slidesToScroll: isMobileResolution ? 1 : 3,
|
||||||
@@ -68,30 +76,28 @@ const SliderComponent = () => {
|
|||||||
prevArrow: <PrevArrow />,
|
prevArrow: <PrevArrow />,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Если загружаем данные, показываем статические объекты как fallback
|
|
||||||
if (loading || objects.length === 0) {
|
if (loading || objects.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="slider-objects">
|
<div className="slider-objects">
|
||||||
<Slider {...{...settings, dots: false, infinite: true}}>
|
<Slider {...{ ...settings, dots: false, infinite: true }}>
|
||||||
<Link className="objects-link" to="/apartament">
|
|
||||||
<Object
|
<Object
|
||||||
image={objectPicOne}
|
image={objectPicOne}
|
||||||
price="1 234 567 ₽"
|
price="2 500 000 ₽"
|
||||||
desc="1-комн. кв. 34 м"
|
desc="2-комн. кв., 47 м², 1/2 этаж"
|
||||||
address="Ул. Луначарского, Ленинский район"
|
address="Челябинская область, Челябинск, Лазурная улица, 14А"
|
||||||
/>
|
/>
|
||||||
</Link>
|
|
||||||
<Object
|
<Object
|
||||||
image={objectPicTwo}
|
image={objectPicTwo}
|
||||||
price="1 234 567 ₽"
|
price="4 200 000 ₽"
|
||||||
desc="1-комн. кв. 34 м"
|
desc="3-комн. кв., 77 м², 4/4 этаж"
|
||||||
address="Ул. Зари, Вагонка"
|
address="Челябинская область, Челябинск, улица Ярослава Гашека, 20"
|
||||||
/>
|
/>
|
||||||
<Object
|
<Object
|
||||||
image={objectPicThree}
|
image={objectPicThree}
|
||||||
price="1 234 567 ₽"
|
price="4 900 000 ₽"
|
||||||
desc="1-комн. кв. 34 м"
|
desc="Квартира-студия, 27,6 м², 19/25 этаж"
|
||||||
address="Ул. Солнечная, Заречный район"
|
address="Свердловская область, Екатеринбург, улица Студенческая, 80"
|
||||||
/>
|
/>
|
||||||
</Slider>
|
</Slider>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,14 +108,13 @@ const SliderComponent = () => {
|
|||||||
<div className="slider-objects">
|
<div className="slider-objects">
|
||||||
<Slider {...settings}>
|
<Slider {...settings}>
|
||||||
{objects.map((object) => (
|
{objects.map((object) => (
|
||||||
<Link key={object.id} className="objects-link" to={`/apartament/${object.id}`}>
|
|
||||||
<Object
|
<Object
|
||||||
|
key={object.id}
|
||||||
image={object.photoUrl}
|
image={object.photoUrl}
|
||||||
price={object.price}
|
price={object.price}
|
||||||
desc={object.title}
|
desc={object.title}
|
||||||
address={object.address}
|
address={object.address}
|
||||||
/>
|
/>
|
||||||
</Link>
|
|
||||||
))}
|
))}
|
||||||
</Slider>
|
</Slider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ export const CONTACTS = {
|
|||||||
postalCode: "454052",
|
postalCode: "454052",
|
||||||
full: "ул. Комаровского, 4А, офис 210, Челябинск, 454052",
|
full: "ул. Комаровского, 4А, офис 210, Челябинск, 454052",
|
||||||
},
|
},
|
||||||
office: {
|
|
||||||
street: "Ленина, д. 60 В, оф. 701",
|
|
||||||
city: "Челябинск",
|
|
||||||
description: "Вход в офис со двора",
|
|
||||||
full: "Ленина, д. 60 В, оф. 701, Челябинск",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
coordinates: [55.242355, 61.37697],
|
coordinates: [55.242355, 61.37697],
|
||||||
|
|||||||
@@ -90,10 +90,8 @@ function Office() {
|
|||||||
<div className="office-info">
|
<div className="office-info">
|
||||||
<p className="office-info__para_address">
|
<p className="office-info__para_address">
|
||||||
<span>Адрес:</span>
|
<span>Адрес:</span>
|
||||||
{CONTACTS.address.office.full}
|
{CONTACTS.address.main.full}
|
||||||
<span className="office-info__desc">
|
<span className="office-info__desc">Вход в офис со двора</span>
|
||||||
{CONTACTS.address.office.description}
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
<p className="office-info__para">
|
<p className="office-info__para">
|
||||||
<span>Телефон:</span>
|
<span>Телефон:</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user