From 3f59295b842797b997fe2c3fa93bd68ab3c0191f Mon Sep 17 00:00:00 2001 From: Madara0330E Date: Sat, 19 Jul 2025 22:28:45 +0500 Subject: [PATCH] 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. --- src/components/Object/Object.jsx | 59 +++- src/components/Object/Object.scss | 341 +++++++++++++++-------- src/components/Sliders/SliderObjects.jsx | 203 +++++++------- src/config/contacts.js | 6 - src/pages/Office/Office.jsx | 6 +- 5 files changed, 387 insertions(+), 228 deletions(-) diff --git a/src/components/Object/Object.jsx b/src/components/Object/Object.jsx index 5ab7ad6..5e45bac 100644 --- a/src/components/Object/Object.jsx +++ b/src/components/Object/Object.jsx @@ -1,16 +1,55 @@ -import './Object.scss'; +import "./Object.scss"; +import { useState } from "react"; function Object(props) { - return ( -
- квартира -
-

{props.price}

-

{props.desc}

-

{props.address}

-
+ 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 ( +
+ квартира +
+
+

{props.price}

+

+ {props.desc} +

+

+ {truncateAddress(props.address)} +

- ); + {needsExpansion && ( + + )} +
+
+ ); } export { Object }; diff --git a/src/components/Object/Object.scss b/src/components/Object/Object.scss index b2deba1..1d64e70 100644 --- a/src/components/Object/Object.scss +++ b/src/components/Object/Object.scss @@ -1,158 +1,281 @@ -@import '../../styles/vars.scss'; +@import "../../styles/vars.scss"; .item__info p { - margin: 0 0 12px 0; + margin: 0 0 12px 0; } .item { - display: flex; - flex-direction: column; - // width: 381px; - // height: 381px; - margin: 0 20px; - border-radius: 15px; - background-color: white; + display: flex; + flex-direction: column; + margin: 0 20px; + border-radius: 15px; + background-color: white; + min-height: 450px; + transition: height 0.3s ease; - @media (max-width: 1300px) { - height: 381px; - } + &--expanded { + height: auto; + min-height: 450px; + } - @media (max-width: 1200px) { - height: 361px; - } + @media (min-width: 1800px) { + min-height: 480px; - @media (max-width: 1100px) { - height: 341px; + &--expanded { + min-height: 480px; } + } - @media (max-width: $laptopWidth) { - height: 321px; - margin: 0 15px; - } + @media (max-width: 1300px) { + min-height: 430px; - @media (max-width: $tabletWidth) { - height: 505px; + &--expanded { + min-height: 430px; } + } - @media (max-width: 740px) { - height: 473px; - } + @media (max-width: 1200px) { + min-height: 410px; - @media (max-width: 690px) { - height: 435px; + &--expanded { + min-height: 410px; } + } - @media (max-width: 635px) { - height: 400px; - } + @media (max-width: 1100px) { + min-height: 390px; - @media (max-width: 570px) { - height: 361px; + &--expanded { + min-height: 390px; } + } - @media (max-width: $mobileWidth) { - height: 381px; - } + @media (max-width: $laptopWidth) { + min-height: 370px; + margin: 0 15px; - @media (max-width: 420px) { - height: 361px; + &--expanded { + min-height: 370px; } + } + + @media (max-width: $tabletWidth) { + min-height: 500px; + + &--expanded { + min-height: 500px; + } + } + + @media (max-width: 740px) { + min-height: 480px; + + &--expanded { + min-height: 480px; + } + } + + @media (max-width: 690px) { + min-height: 460px; + + &--expanded { + min-height: 460px; + } + } + + @media (max-width: 635px) { + min-height: 440px; + + &--expanded { + min-height: 440px; + } + } + + @media (max-width: 570px) { + min-height: 420px; + + &--expanded { + min-height: 420px; + } + } + + @media (max-width: $mobileWidth) { + min-height: 400px; + margin: 0 10px; + + &--expanded { + min-height: 400px; + } + } + + @media (max-width: 420px) { + min-height: 380px; + + &--expanded { + min-height: 380px; + } + } } - .item__image:hover { - text-decoration: none; + text-decoration: none; } .item__image { - border-top-right-radius: 15px; - border-top-left-radius: 15px; - width: 100%; + border-top-right-radius: 15px; + border-top-left-radius: 15px; + width: 100%; + height: 250px; + object-fit: cover; + object-position: center; + display: block; + + @media (max-width: 1300px) { + height: 230px; + } + + @media (max-width: 1200px) { + height: 210px; + } + + @media (max-width: 1100px) { + height: 190px; + } + + @media (max-width: $laptopWidth) { + height: 170px; + } + + @media (max-width: $tabletWidth) { + height: 300px; + } + + @media (max-width: 740px) { + height: 280px; + } + + @media (max-width: 690px) { height: 250px; - object-fit: cover; - object-position: center; - display: block; + } - @media (max-width: 1300px) { - height: 230px; - } + @media (max-width: 635px) { + height: 220px; + } - @media (max-width: 1200px) { - height: 210px; - } + @media (max-width: 570px) { + height: 190px; + } - @media (max-width: 1100px) { - height: 190px; - } + @media (max-width: $mobileWidth) { + height: 210px; + } - @media (max-width: $laptopWidth) { - height: 170px; - } - - @media (max-width: $tabletWidth) { - height: 300px; - } - - @media (max-width: 740px) { - height: 280px; - } - - @media (max-width: 690px) { - height: 250px; - } - - @media (max-width: 635px) { - height: 220px; - } - - @media (max-width: 570px) { - height: 190px; - } - - @media (max-width: $mobileWidth) { - height: 210px; - } - - @media (max-width: 420px) { - height: 190px; - } + @media (max-width: 420px) { + height: 190px; + } } .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 { - font-size: 25px; + font-size: 25px; - @media (min-width: 1800px) { - font-size: 29px; - } + @media (min-width: 1800px) { + font-size: 29px; + } - @media (min-width: 768.98px) and (max-width: $laptopWidth) { - font-size: 23px; - } + @media (min-width: 768.98px) and (max-width: $laptopWidth) { + font-size: 23px; + } } .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) { - font-size: 24px; - } + @media (min-width: 1800px) { + font-size: 24px; + } - @media (min-width: 768.98px) and (max-width: $laptopWidth) { - font-size: 18px; - } + @media (min-width: 768.98px) and (max-width: $laptopWidth) { + font-size: 18px; + } } .item__address { - font-size: 17px; + font-size: 17px; + line-height: 1.3; + word-wrap: break-word; + hyphens: auto; - @media (min-width: 1800px) { - font-size: 21px; - } + @media (min-width: 1800px) { + font-size: 21px; + } - @media (min-width: 768.98px) and (max-width: $laptopWidth) { - font-size: 16px; - } -} \ No newline at end of file + @media (min-width: 768.98px) and (max-width: $laptopWidth) { + 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; + } +} diff --git a/src/components/Sliders/SliderObjects.jsx b/src/components/Sliders/SliderObjects.jsx index fc04b00..02dd93f 100644 --- a/src/components/Sliders/SliderObjects.jsx +++ b/src/components/Sliders/SliderObjects.jsx @@ -1,119 +1,124 @@ -import React, { useState, useEffect } from 'react'; -import Slider from 'react-slick'; -import { Link } from 'react-router-dom'; -import { useMediaQuery } from 'react-responsive'; +import React, { useState, useEffect } from "react"; +import Slider from "react-slick"; +import { useMediaQuery } from "react-responsive"; -import 'slick-carousel/slick/slick.css'; -import 'slick-carousel/slick/slick-theme.css'; -import './SliderObjects.scss'; -import { Object } from '../Object/Object'; -import { API_CONFIG } from '../../config/contacts'; +import "slick-carousel/slick/slick.css"; +import "slick-carousel/slick/slick-theme.css"; +import "./SliderObjects.scss"; +import { Object } from "../Object/Object"; +import { API_CONFIG } from "../../config/contacts"; -import objectPicOne from '../../assets/images/apartaments/image-44.jpg'; -import objectPicTwo from '../../assets/images/apartaments/image-45.jpg'; -import objectPicThree from '../../assets/images/apartaments/image-46.jpg'; +import objectPicOne from "../../assets/images/apartaments/image-44.jpg"; +import objectPicTwo from "../../assets/images/apartaments/image-45.jpg"; +import objectPicThree from "../../assets/images/apartaments/image-46.jpg"; const NextArrow = ({ onClick }) => { - return ( -
- ); + return ( +
+ ); }; const PrevArrow = ({ onClick }) => { - return ( -
- ); + return ( +
+ ); }; const SliderComponent = () => { - const [objects, setObjects] = useState([]); - const [loading, setLoading] = useState(true); - - const isMobileResolution = useMediaQuery({ maxWidth: 768 }); - - // Загрузка данных с API - useEffect(() => { - const fetchObjects = async () => { - try { - const response = await fetch(API_CONFIG.getFullURL(API_CONFIG.endpoints.rental), { - method: 'GET', - headers: { - 'accept': '*/*' - } - }); + const [objects, setObjects] = useState([]); + const [loading, setLoading] = useState(true); - if (response.ok) { - const data = await response.json(); - setObjects(data); - } - } catch (error) { - console.error('Ошибка при загрузке объектов:', error); - // В случае ошибки оставляем пустой массив, компонент не сломается - } finally { - setLoading(false); - } - }; + const isMobileResolution = useMediaQuery({ maxWidth: 768 }); - fetchObjects(); - }, []); - - const settings = { - dots: objects.length > (isMobileResolution ? 1 : 3), // Показываем точки если объектов больше чем влезает - infinite: objects.length > (isMobileResolution ? 1 : 3), // Бесконечная прокрутка только если объектов достаточно - speed: 500, - slidesToShow: isMobileResolution ? 1 : 3, - slidesToScroll: isMobileResolution ? 1 : 3, - arrows: true, - nextArrow: , - prevArrow: , + // Загрузка данных с API + useEffect(() => { + const fetchObjects = async () => { + try { + const response = await fetch( + API_CONFIG.getFullURL(API_CONFIG.endpoints.rental), + { + method: "GET", + headers: { + accept: "*/*", + }, + } + ); + + if (response.ok) { + const data = await response.json(); + setObjects(data); + } + } catch (error) { + console.error("Ошибка при загрузке объектов:", error); + + } finally { + setLoading(false); + } }; - // Если загружаем данные, показываем статические объекты как fallback - if (loading || objects.length === 0) { - return ( -
- - - - - - - - - ); - } + fetchObjects(); + }, []); + const settings = { + dots: objects.length > (isMobileResolution ? 1 : 3), + infinite: objects.length > (isMobileResolution ? 1 : 3), + speed: 500, + slidesToShow: isMobileResolution ? 1 : 3, + slidesToScroll: isMobileResolution ? 1 : 3, + arrows: true, + nextArrow: , + prevArrow: , + }; + + + if (loading || objects.length === 0) { return ( -
- - {objects.map((object) => ( - - - - ))} - - +
+ + + + + + ); + } + + return ( +
+ + {objects.map((object) => ( + + ))} + + + ); }; export { SliderComponent }; diff --git a/src/config/contacts.js b/src/config/contacts.js index 6dca52a..432d72e 100644 --- a/src/config/contacts.js +++ b/src/config/contacts.js @@ -15,12 +15,6 @@ export const CONTACTS = { postalCode: "454052", full: "ул. Комаровского, 4А, офис 210, Челябинск, 454052", }, - office: { - street: "Ленина, д. 60 В, оф. 701", - city: "Челябинск", - description: "Вход в офис со двора", - full: "Ленина, д. 60 В, оф. 701, Челябинск", - }, }, coordinates: [55.242355, 61.37697], diff --git a/src/pages/Office/Office.jsx b/src/pages/Office/Office.jsx index 1372afe..c4bee58 100644 --- a/src/pages/Office/Office.jsx +++ b/src/pages/Office/Office.jsx @@ -90,10 +90,8 @@ function Office() {

Адрес: - {CONTACTS.address.office.full} - - {CONTACTS.address.office.description} - + {CONTACTS.address.main.full} + Вход в офис со двора

Телефон: