add persons, / footer(contacts) / main car page
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
import { Routes, Route } from 'react-router-dom'
|
import { Routes, Route } from 'react-router-dom'
|
||||||
import MainPage from './pages/MainPage.tsx'
|
import MainPage from './pages/MainPage.tsx'
|
||||||
|
import CarPage from './pages/CarPage.tsx'
|
||||||
import Header from './components/Header.tsx'
|
import Header from './components/Header.tsx'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@ -15,6 +16,10 @@ function App() {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/car/:id"
|
||||||
|
element={<CarPage />}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -2,10 +2,15 @@ import { Box } from "@mui/material";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useResponsive } from "../theme/useResponsive";
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
|
|
||||||
function Divider() {
|
interface DividerProps {
|
||||||
|
marginTopDivider?: string | "1vw";
|
||||||
|
marginBottomDivider?: string | "1vw";
|
||||||
|
}
|
||||||
|
|
||||||
|
function Divider({ marginTopDivider, marginBottomDivider }: DividerProps) {
|
||||||
const { isMobile } = useResponsive();
|
const { isMobile } = useResponsive();
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "center", bgcolor: "#fff" }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -14,9 +19,9 @@ function Divider() {
|
|||||||
height: isMobile ? "0.3vw" : "0.15vw",
|
height: isMobile ? "0.3vw" : "0.15vw",
|
||||||
backgroundColor: "rgba(220, 220, 220, 1)",
|
backgroundColor: "rgba(220, 220, 220, 1)",
|
||||||
borderRadius: "1vw",
|
borderRadius: "1vw",
|
||||||
position: "absolute",
|
// position: "absolute",
|
||||||
mt: "1vw",
|
mt: marginTopDivider,
|
||||||
mb: "1vw",
|
mb: marginBottomDivider,
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -62,7 +62,7 @@ const Feedback: React.FC<FeedbackProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [phone, setPhone] = useState("+7");
|
const [phone, setPhone] = useState("+7");
|
||||||
const [country, setCountry] = useState("Европа");
|
const [country, setCountry] = useState("США");
|
||||||
const [budget, setBudget] = useState("до 3 млн");
|
const [budget, setBudget] = useState("до 3 млн");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const [agreeToPolicy, setAgreeToPolicy] = useState(false);
|
const [agreeToPolicy, setAgreeToPolicy] = useState(false);
|
||||||
@ -271,35 +271,6 @@ const Feedback: React.FC<FeedbackProps> = ({
|
|||||||
value={country}
|
value={country}
|
||||||
onChange={(e) => setCountry(e.target.value)}
|
onChange={(e) => setCountry(e.target.value)}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
|
||||||
value="Европа"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
sx={{
|
|
||||||
color: "#C27664",
|
|
||||||
"&.Mui-checked": { color: "#C27664" },
|
|
||||||
"& .MuiSvgIcon-root": {
|
|
||||||
fontSize: isMobile ? "4.5vw" : "1.5vw",
|
|
||||||
},
|
|
||||||
padding: "0.5vw",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: isMobile ? "3vw" : "1.1vw",
|
|
||||||
fontFamily: "Unbounded",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Европа
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
marginRight: isMobile ? "2.5vw" : "1.5vw",
|
|
||||||
ml: "0vw",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="США"
|
value="США"
|
||||||
control={
|
control={
|
||||||
|
@ -14,22 +14,25 @@ import logo from "../assets/icon/autobro.png";
|
|||||||
import { useResponsive } from "../theme/useResponsive";
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
import Feedback from "./Feedback";
|
import Feedback from "./Feedback";
|
||||||
import { scrollToAnchor } from "../utils/scrollUtils";
|
import { scrollToAnchor } from "../utils/scrollUtils";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { isMobile } = useResponsive();
|
const { isMobile } = useResponsive();
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
||||||
const [scrolled, setScrolled] = useState(false);
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
|
// { title: "", anchor: "#main" },
|
||||||
{ title: "О нас", anchor: "#about-us" },
|
{ title: "О нас", anchor: "#about-us" },
|
||||||
|
{ title: "Этапы работы", anchor: "#stages" },
|
||||||
|
{ title: "В наличии", anchor: "#available" },
|
||||||
{ title: "Калькулятор", anchor: "#calculator" },
|
{ title: "Калькулятор", anchor: "#calculator" },
|
||||||
|
{ title: "Команда", anchor: "#team" },
|
||||||
{ title: "Отзывы", anchor: "#reviews" },
|
{ title: "Отзывы", anchor: "#reviews" },
|
||||||
{ title: "Контакты", anchor: "#contacts" },
|
{ title: "Контакты", anchor: "#contacts" },
|
||||||
{ title: "В наличии", anchor: "#available" },
|
// { title: "Доставленные авто", anchor: "#delivered" },
|
||||||
{ title: "Команда", anchor: "#team" },
|
|
||||||
{ title: "Доставленные авто", anchor: "#delivered" },
|
|
||||||
{ title: "Этапы работы", anchor: "#stages" },
|
|
||||||
{ title: " ", anchor: "#main" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Отслеживание скролла
|
// Отслеживание скролла
|
||||||
@ -62,7 +65,11 @@ const Header = () => {
|
|||||||
|
|
||||||
// Используем глобальную функцию из utils
|
// Используем глобальную функцию из utils
|
||||||
const handleScrollToAnchor = (anchor: string) => {
|
const handleScrollToAnchor = (anchor: string) => {
|
||||||
|
if (location.pathname.startsWith("/car")) {
|
||||||
|
navigate("/");
|
||||||
|
} else {
|
||||||
scrollToAnchor(anchor, setDrawerOpen, isMobile);
|
scrollToAnchor(anchor, setDrawerOpen, isMobile);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -153,7 +160,7 @@ const Header = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Пустой блок для компенсации фиксированного хедера */}
|
{/* Пустой блок для компенсации фиксированного хедера */}
|
||||||
<Box sx={{ height: isMobile ? "15vw" : "10vw", bgcolor: "#2D2D2D" }} />
|
<Box sx={{ height: isMobile ? "15vw" : location.pathname.startsWith("/car") ? "5vw" : "10vw", bgcolor: "#2D2D2D" }} />
|
||||||
|
|
||||||
{/* Мобильное меню */}
|
{/* Мобильное меню */}
|
||||||
<Drawer anchor="right" open={drawerOpen} onClose={toggleDrawer(false)}>
|
<Drawer anchor="right" open={drawerOpen} onClose={toggleDrawer(false)}>
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
Collapse,
|
Collapse,
|
||||||
Paper,
|
Paper,
|
||||||
Slide,
|
Slide,
|
||||||
|
Checkbox,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useResponsive } from "../theme/useResponsive";
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
import russia from "../assets/emoji/russia.png";
|
import russia from "../assets/emoji/russia.png";
|
||||||
@ -330,7 +331,6 @@ function CalculatorPage() {
|
|||||||
scrollMarginTop: isMobile ? "15vw" : "10vw",
|
scrollMarginTop: isMobile ? "15vw" : "10vw",
|
||||||
maxWidth: "100vw",
|
maxWidth: "100vw",
|
||||||
overflowX: "hidden",
|
overflowX: "hidden",
|
||||||
pb: "25vw",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -1125,7 +1125,7 @@ function CalculatorPage() {
|
|||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Checkbox
|
||||||
checked={enhancedPermeability}
|
checked={enhancedPermeability}
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
setEnhancedPermeability(!enhancedPermeability)
|
setEnhancedPermeability(!enhancedPermeability)
|
||||||
|
373
src/pages/CarPage.tsx
Normal file
373
src/pages/CarPage.tsx
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, Button, Grid, CircularProgress, IconButton, Popover } from "@mui/material";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { fetchCarById, getImageUrl, type Car } from "../utils/api";
|
||||||
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
|
import Feedback from "../components/Feedback";
|
||||||
|
import TelegramIcon from "@mui/icons-material/Telegram";
|
||||||
|
import { FaVk } from "react-icons/fa";
|
||||||
|
import WhatsAppIcon from "@mui/icons-material/WhatsApp";
|
||||||
|
import InfoOutlineIcon from '@mui/icons-material/InfoOutline';
|
||||||
|
|
||||||
|
interface CarDetails extends Car {
|
||||||
|
country_of_origin?: string;
|
||||||
|
drive_type?: string;
|
||||||
|
engine_capacity?: number;
|
||||||
|
engine_power?: number;
|
||||||
|
engine_type?: string;
|
||||||
|
electric_motor_power?: number;
|
||||||
|
hybrid_type?: string;
|
||||||
|
power_ratio?: string;
|
||||||
|
base_price?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CarPage() {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const { isMobile } = useResponsive();
|
||||||
|
const [car, setCar] = useState<CarDetails | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
||||||
|
|
||||||
|
const translateCountry = (country: string) => {
|
||||||
|
switch (country) {
|
||||||
|
case "Russia":
|
||||||
|
return "Россия";
|
||||||
|
case "China":
|
||||||
|
return "Китай";
|
||||||
|
case "Korea":
|
||||||
|
return "Корея";
|
||||||
|
case "USA":
|
||||||
|
return "США";
|
||||||
|
case "Россия":
|
||||||
|
return "Россия";
|
||||||
|
case "Китай":
|
||||||
|
return "Китай";
|
||||||
|
case "Корея":
|
||||||
|
return "Корея";
|
||||||
|
case "США":
|
||||||
|
return "США";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const translateWheelDrive = (wheelDrive: string) => {
|
||||||
|
switch (wheelDrive) {
|
||||||
|
case "AWD":
|
||||||
|
return "Полный";
|
||||||
|
case "RWD":
|
||||||
|
return "Задний";
|
||||||
|
case "FWD":
|
||||||
|
return "Передний";
|
||||||
|
case "передний":
|
||||||
|
return "Передний";
|
||||||
|
case "задний":
|
||||||
|
return "Задний";
|
||||||
|
case "полный":
|
||||||
|
return "Полный";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadCar = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetchCarById(Number(id));
|
||||||
|
console.log("Полученные данные автомобиля:", response.car);
|
||||||
|
setCar(response.car);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка при загрузке данных:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadCar();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const handleOpenFeedback = () => {
|
||||||
|
setFeedbackOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFeedback = () => {
|
||||||
|
setFeedbackOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Проверяем, является ли автомобиль электрическим
|
||||||
|
const isElectric = car?.engine_type === "электрический";
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "100vh",
|
||||||
|
bgcolor: "#2D2D2D"
|
||||||
|
}}>
|
||||||
|
<CircularProgress sx={{ color: "#C27664" }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!car) {
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "70vh",
|
||||||
|
bgcolor: "#2D2D2D",
|
||||||
|
color: "white"
|
||||||
|
}}>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Автомобиль не найден
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
Запрошенный автомобиль не существует или был удален.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Отрисовка автомобиля:", car);
|
||||||
|
console.log("Тип двигателя:", car.engine_type);
|
||||||
|
console.log("Объем двигателя:", car.engine_capacity);
|
||||||
|
console.log("Является электрическим:", isElectric);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ bgcolor: "#2D2D2D", color: "white", py: isMobile ? "10vw" : "2vw", pb: "12.6vw", overflow: "hidden", userSelect: "none" }}>
|
||||||
|
<Box sx={{ maxWidth: "90vw", margin: "0 auto" }}>
|
||||||
|
{/* Название автомобиля */}
|
||||||
|
{/* <Typography
|
||||||
|
variant="h3"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "8vw" : "4vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: "2vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Автомобиль - <span style={{ color: "#C27664" }}>{car.name}</span>
|
||||||
|
</Typography> */}
|
||||||
|
|
||||||
|
<Grid container spacing={isMobile ? 4 : 5}>
|
||||||
|
{/* Изображение автомобиля */}
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "54vw",
|
||||||
|
height: isMobile ? "60vw" : "30vw",
|
||||||
|
backgroundImage: `url(${getImageUrl(car.image)})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
borderRadius: "2vw",
|
||||||
|
boxShadow: "0px 0px 20px rgba(0, 0, 0, 0.5)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<InfoOutlineIcon sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "8vw",
|
||||||
|
left: "54vw",
|
||||||
|
width: "3vw",
|
||||||
|
height: "3vw",
|
||||||
|
borderRadius: "1vw",
|
||||||
|
zIndex: 1001,
|
||||||
|
color: "#2D2D2D",
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": {
|
||||||
|
scale: "1.1",
|
||||||
|
"&:hover + .info-box": {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
}} />
|
||||||
|
<Box className="info-box" sx={{
|
||||||
|
padding: "1vw",
|
||||||
|
position: "absolute",
|
||||||
|
top: "10vw",
|
||||||
|
left: "7vw",
|
||||||
|
width: "46vw",
|
||||||
|
backgroundColor: "#2D2D2D",
|
||||||
|
borderRadius: "1.5vw",
|
||||||
|
zIndex: 1000,
|
||||||
|
opacity: 0,
|
||||||
|
transition: "opacity 0.3s ease",
|
||||||
|
boxShadow: "0vw 0vw 1vw 0vw rgba(0, 0, 0, 0.5)",
|
||||||
|
}}>
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.6vw", fontFamily: "Unbounded" }}>
|
||||||
|
{car.name} {car.year} года выпуска с пробегом {car.mileage.toLocaleString()} км,
|
||||||
|
{car.engine_type && ` ${car.engine_type} двигатель`}
|
||||||
|
{!isElectric && car.engine_capacity ? ` объёмом ${car.engine_capacity} л` : ""}
|
||||||
|
{car.engine_power && `, мощностью ${car.engine_power} л.с.`}
|
||||||
|
{car.drive_type && ` Привод: ${translateWheelDrive(car.drive_type)}.`}
|
||||||
|
{car.country_of_origin && ` Страна производства: ${translateCountry(car.country_of_origin)}.`}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Информация об автомобиле */}
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Box sx={{
|
||||||
|
backgroundColor: "#333333",
|
||||||
|
borderRadius: "2vw",
|
||||||
|
p: isMobile ? "5vw" : "2vw",
|
||||||
|
height: isElectric ? "100%" : "91%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "space-between"
|
||||||
|
}}>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "6vw" : "3vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: "1vw",
|
||||||
|
color: "#C27664"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{car.price.toLocaleString()}₽
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.2vw", mb: "0.5vw", fontFamily: "Unbounded" }}>
|
||||||
|
Год выпуска: <strong style={{ color: "#C27664" }}>{car.year}</strong>
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.2vw", mb: "0.5vw", fontFamily: "Unbounded" }}>
|
||||||
|
Пробег: <strong style={{ color: "#C27664" }}>{car.mileage.toLocaleString()} км</strong>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{!isElectric && car.engine_capacity !== null && car.engine_capacity !== undefined && (
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.2vw", mb: "0.5vw", fontFamily: "Unbounded" }}>
|
||||||
|
Объём двигателя: <strong style={{ color: "#C27664" }}>{car.engine_capacity} л</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{car.engine_power && isElectric && (
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.2vw", mb: "0.5vw", fontFamily: "Unbounded" }}>
|
||||||
|
Мощность: <strong style={{ color: "#C27664" }}>{Math.round(car.engine_power * 0.735499)} кВт ({car.engine_power} л.с)</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{car.engine_power && !isElectric && (
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.2vw", mb: "0.5vw", fontFamily: "Unbounded" }}>
|
||||||
|
Мощность: <strong style={{ color: "#C27664" }}>{car.engine_power} л.с.</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{car.engine_type && (
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.2vw", mb: "0.5vw", fontFamily: "Unbounded" }}>
|
||||||
|
Тип двигателя: <strong style={{ color: "#C27664" }}>{car.engine_type}</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{car.drive_type && (
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.2vw", mb: "0.5vw", fontFamily: "Unbounded" }}>
|
||||||
|
Привод: <strong style={{ color: "#C27664" }}>{translateWheelDrive(car.drive_type)}</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{car.country_of_origin && (
|
||||||
|
<Typography sx={{ fontSize: isMobile ? "3.5vw" : "1.2vw", mb: "0.5vw", fontFamily: "Unbounded" }}>
|
||||||
|
Страна производства: <strong style={{ color: "#C27664" }}>{translateCountry(car.country_of_origin)}</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ mt: isMobile ? "5vw" : "2vw" }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleOpenFeedback}
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
bgcolor: "#C27664",
|
||||||
|
color: "white",
|
||||||
|
fontSize: isMobile ? "4vw" : "1.5vw",
|
||||||
|
padding: isMobile ? "3vw" : "1vw",
|
||||||
|
textTransform: "none",
|
||||||
|
borderRadius: "1vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
"&:hover": { bgcolor: "#945B4D" },
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
mb: "1vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Заказать автомобиль
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: "1vw",
|
||||||
|
mt: "1vw"
|
||||||
|
}}>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: "#C27664",
|
||||||
|
"&:hover": { color: "#945B4D" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaVk size={isMobile ? "7vw" : "2vw"} />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
onClick={() => window.open("https://t.me/autoBROcn", "_blank")}
|
||||||
|
sx={{
|
||||||
|
color: "#C27664",
|
||||||
|
"&:hover": { color: "#945B4D" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TelegramIcon sx={{ fontSize: isMobile ? "8vw" : "2.5vw" }} />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: "#C27664",
|
||||||
|
"&:hover": { color: "#945B4D" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<WhatsAppIcon sx={{ fontSize: isMobile ? "8vw" : "2.5vw" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Дополнительная информация */}
|
||||||
|
<Box sx={{ mt: isMobile ? "8vw" : isElectric ? "7.3vw" : "4vw" }}>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "6vw" : "2.2vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: "2vw",
|
||||||
|
mt: "-10vw",
|
||||||
|
backgroundColor: "#333333",
|
||||||
|
borderRadius: "2vw",
|
||||||
|
p: "2vw",
|
||||||
|
maxWidth: "50vw",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{car.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Feedback
|
||||||
|
open={feedbackOpen}
|
||||||
|
onClose={handleCloseFeedback}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CarPage;
|
200
src/pages/ContactsPage.tsx
Normal file
200
src/pages/ContactsPage.tsx
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Box, Typography, Button, IconButton } from "@mui/material";
|
||||||
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
|
import Feedback from "../components/Feedback";
|
||||||
|
import { scrollToAnchor } from "../utils/scrollUtils";
|
||||||
|
import PhoneIcon from "@mui/icons-material/Phone";
|
||||||
|
import EmailIcon from "@mui/icons-material/Email";
|
||||||
|
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||||
|
import TelegramIcon from "@mui/icons-material/Telegram";
|
||||||
|
import { FaVk } from "react-icons/fa";
|
||||||
|
import WhatsAppIcon from "@mui/icons-material/WhatsApp";
|
||||||
|
|
||||||
|
function ContactsPage() {
|
||||||
|
const { isMobile } = useResponsive();
|
||||||
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
||||||
|
const setDrawerOpen = () => {};
|
||||||
|
|
||||||
|
const handleOpenFeedback = () => {
|
||||||
|
setFeedbackOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFeedback = () => {
|
||||||
|
setFeedbackOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScrollToAnchor = (anchor: string) => {
|
||||||
|
scrollToAnchor(anchor, setDrawerOpen, isMobile);
|
||||||
|
};
|
||||||
|
|
||||||
|
const contactIconStyle = {
|
||||||
|
fontSize: isMobile ? "6vw" : "3vw",
|
||||||
|
color: "#C27664",
|
||||||
|
marginRight: "1vw",
|
||||||
|
};
|
||||||
|
|
||||||
|
const contactItemStyle = {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: isMobile ? "4vw" : "2vw",
|
||||||
|
};
|
||||||
|
|
||||||
|
const contactTextStyle = {
|
||||||
|
fontSize: isMobile ? "4vw" : "1.5vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
};
|
||||||
|
|
||||||
|
const socialIconStyle = {
|
||||||
|
color: "#C27664",
|
||||||
|
borderRadius: "50%",
|
||||||
|
width: isMobile ? "10vw" : "4vw",
|
||||||
|
height: isMobile ? "10vw" : "4vw",
|
||||||
|
border: "2px solid #C27664",
|
||||||
|
margin: isMobile ? "0 3vw" : "0 1vw",
|
||||||
|
"&:hover": { color: "#945B4D", borderColor: "#945B4D" },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
id="contacts"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "white",
|
||||||
|
color: "black",
|
||||||
|
padding: "5vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "80vw",
|
||||||
|
margin: "0 auto",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "8vw" : "4rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: "2rem",
|
||||||
|
textWrap: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
НАШИ <span style={{ color: "#C27664" }}>КОНТАКТЫ</span>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: isMobile ? "column" : "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginBottom: "3vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
textAlign: "left",
|
||||||
|
padding: isMobile ? "5vw 0" : "2vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={contactItemStyle}>
|
||||||
|
<PhoneIcon sx={contactIconStyle} />
|
||||||
|
<Typography sx={contactTextStyle}>8 (965) 372-51-90</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={contactItemStyle}>
|
||||||
|
<EmailIcon sx={contactIconStyle} />
|
||||||
|
<Typography sx={contactTextStyle}>info@autobro.ru</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={contactItemStyle}>
|
||||||
|
<LocationOnIcon sx={contactIconStyle} />
|
||||||
|
<Typography sx={contactTextStyle}>
|
||||||
|
г.Москва, станция метро "Домодедово"
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginTop: isMobile ? "8vw" : "4vw",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "4vw" : "1.5vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
marginRight: isMobile ? "3vw" : "1vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Мы в соцсетях:
|
||||||
|
</Typography>
|
||||||
|
<IconButton sx={socialIconStyle}>
|
||||||
|
<FaVk size={isMobile ? "5vw" : "2vw"} />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => window.open("https://t.me/autoBROcn", "_blank")}
|
||||||
|
sx={socialIconStyle}
|
||||||
|
>
|
||||||
|
<TelegramIcon sx={{ fontSize: isMobile ? "5vw" : "2vw" }} />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton sx={socialIconStyle}>
|
||||||
|
<WhatsAppIcon sx={{ fontSize: isMobile ? "5vw" : "2vw" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: isMobile ? "5vw 0" : "2vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "5vw" : "2vw",
|
||||||
|
marginBottom: "2vw",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Остались вопросы?
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleOpenFeedback}
|
||||||
|
sx={{
|
||||||
|
bgcolor: "#C27664",
|
||||||
|
color: "white",
|
||||||
|
fontSize: isMobile ? "3vw" : "1.2vw",
|
||||||
|
padding: isMobile ? "3vw 6vw" : "1.2vw 3vw",
|
||||||
|
textTransform: "none",
|
||||||
|
borderRadius: isMobile ? "3vw" : "1.5vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
"&:hover": { bgcolor: "#945B4D" },
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Оставить заявку
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Feedback
|
||||||
|
open={feedbackOpen}
|
||||||
|
onClose={handleCloseFeedback}
|
||||||
|
handleScrollToAnchor={handleScrollToAnchor}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContactsPage;
|
@ -13,7 +13,8 @@ import CarsPage from "./CarsPage";
|
|||||||
import DeliveryPage from "./DeliveryPage";
|
import DeliveryPage from "./DeliveryPage";
|
||||||
import Divider from "../components/Divider";
|
import Divider from "../components/Divider";
|
||||||
import CalculatorPage from "./CalculatorPage";
|
import CalculatorPage from "./CalculatorPage";
|
||||||
|
import TeamPage from "./TeamPage";
|
||||||
|
import ContactsPage from "./ContactsPage";
|
||||||
function MainPage() {
|
function MainPage() {
|
||||||
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
||||||
const setDrawerOpen = () => {};
|
const setDrawerOpen = () => {};
|
||||||
@ -155,7 +156,6 @@ function MainPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
id="contacts"
|
|
||||||
sx={{
|
sx={{
|
||||||
// position: "absolute",
|
// position: "absolute",
|
||||||
// top: isMobile ? "18.3vw" : "13.3vw",
|
// top: isMobile ? "18.3vw" : "13.3vw",
|
||||||
@ -229,6 +229,10 @@ function MainPage() {
|
|||||||
<DeliveryPage />
|
<DeliveryPage />
|
||||||
<Divider />
|
<Divider />
|
||||||
<CalculatorPage />
|
<CalculatorPage />
|
||||||
|
<Divider marginTopDivider={isMobile ? "10vw" : "1vw"} marginBottomDivider={isMobile ? "10vw" : "1vw"}/>
|
||||||
|
<TeamPage />
|
||||||
|
<Divider />
|
||||||
|
<ContactsPage />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
276
src/pages/TeamPage.tsx
Normal file
276
src/pages/TeamPage.tsx
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, CircularProgress } from "@mui/material";
|
||||||
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
|
import { fetchTeam, getImageUrl } from "../utils/api";
|
||||||
|
import type { TeamMember } from "../utils/api";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
|
||||||
|
function TeamPage() {
|
||||||
|
const { isMobile } = useResponsive();
|
||||||
|
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
|
|
||||||
|
// Загрузка данных о команде с бэкенда
|
||||||
|
useEffect(() => {
|
||||||
|
const loadTeam = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await fetchTeam();
|
||||||
|
setTeamMembers(response.staff);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Ошибка при загрузке данных о команде:", err);
|
||||||
|
setError("Не удалось загрузить информацию о команде");
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadTeam();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Получаем индексы для отображения (центральный и боковые)
|
||||||
|
const getVisibleIndices = () => {
|
||||||
|
if (teamMembers.length === 0) return { prev: -1, current: -1, next: -1 };
|
||||||
|
|
||||||
|
const prev = activeIndex === 0 ? teamMembers.length - 1 : activeIndex - 1;
|
||||||
|
const current = activeIndex;
|
||||||
|
const next = activeIndex === teamMembers.length - 1 ? 0 : activeIndex + 1;
|
||||||
|
|
||||||
|
return { prev, current, next };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обработчик клика по сотруднику
|
||||||
|
const handleMemberClick = (index: number) => {
|
||||||
|
setActiveIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
id="team"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "white",
|
||||||
|
color: "black",
|
||||||
|
padding: isMobile ? "5vw 0vw 5vw 0vw" : "5vw",
|
||||||
|
scrollMarginTop: isMobile ? "15vw" : "10vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: isMobile ? "100vw" : "90vw",
|
||||||
|
margin: "0 auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "8vw" : "5vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: isMobile ? "center" : "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Наша <span style={{ color: "#C27664" }}>команда</span>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center", my: 5 }}>
|
||||||
|
<CircularProgress sx={{ color: "#C27664" }} />
|
||||||
|
</Box>
|
||||||
|
) : error ? (
|
||||||
|
<Typography color="error" sx={{ textAlign: "center", my: 5 }}>
|
||||||
|
{error}
|
||||||
|
</Typography>
|
||||||
|
) : teamMembers.length > 0 ? (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "relative",
|
||||||
|
height: isMobile ? "110vw" : "40vw",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
overflow: isMobile ? "hidden" : "visible",
|
||||||
|
top: isMobile ? "auto" : "-5vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AnimatePresence>
|
||||||
|
{/* Карусель с сотрудниками */}
|
||||||
|
{teamMembers.map((member, index) => {
|
||||||
|
const { prev, current, next } = getVisibleIndices();
|
||||||
|
let position: "prev" | "current" | "next" | "hidden" = "hidden";
|
||||||
|
|
||||||
|
if (index === prev) position = "prev";
|
||||||
|
else if (index === current) position = "current";
|
||||||
|
else if (index === next) position = "next";
|
||||||
|
|
||||||
|
// Если элемент не видим, не рендерим его
|
||||||
|
if (position === "hidden") return null;
|
||||||
|
|
||||||
|
// Настройки для разных позиций
|
||||||
|
const positionStyles = {
|
||||||
|
prev: {
|
||||||
|
left: isMobile ? "20%" : "30%",
|
||||||
|
top: isMobile ? "40%" : "50%",
|
||||||
|
zIndex: 1,
|
||||||
|
scale: 0.8,
|
||||||
|
opacity: 0.7,
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
left: "50%",
|
||||||
|
top: "50%",
|
||||||
|
zIndex: 2,
|
||||||
|
scale: 1,
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
left: isMobile ? "80%" : "70%",
|
||||||
|
top: isMobile ? "40%" : "50%",
|
||||||
|
zIndex: 1,
|
||||||
|
scale: 0.8,
|
||||||
|
opacity: 0.7,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={member.id}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
x: position === "current" ? "-50%" : position === "prev" ? "-100%" : "0%",
|
||||||
|
y: position === "current" ? "-50%" : "-50%",
|
||||||
|
scale: positionStyles[position].scale,
|
||||||
|
opacity: positionStyles[position].opacity,
|
||||||
|
zIndex: positionStyles[position].zIndex,
|
||||||
|
textWrap: "nowrap",
|
||||||
|
}}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
onClick={() => position !== "current" && handleMemberClick(index)}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: positionStyles[position].left,
|
||||||
|
top: positionStyles[position].top,
|
||||||
|
cursor: position !== "current" ? "pointer" : "default",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: isMobile
|
||||||
|
? position === "current" ? "60vw" : "40vw"
|
||||||
|
: position === "current" ? "25vw" : "20vw",
|
||||||
|
height: isMobile
|
||||||
|
? position === "current" ? "60vw" : "40vw"
|
||||||
|
: position === "current" ? "25vw" : "20vw",
|
||||||
|
// borderRadius: "50%",
|
||||||
|
overflow: "hidden",
|
||||||
|
mb: "1vw",
|
||||||
|
// boxShadow: position === "current" ? "0px 4px 20px rgba(0, 0, 0, 0.2)" : "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||||
|
<img
|
||||||
|
src={getImageUrl(member.photo)}
|
||||||
|
alt={`${member.name} ${member.surname}`}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '30%',
|
||||||
|
background: 'linear-gradient(to top, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%)',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile
|
||||||
|
? position === "current" ? "5vw" : "3.5vw"
|
||||||
|
: position === "current" ? "1.5rem" : "1.2rem",
|
||||||
|
color: "#C27664",
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: 1,
|
||||||
|
visibility: "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.name} {member.surname}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile
|
||||||
|
? position === "current" ? "3.5vw" : "2.5vw"
|
||||||
|
: position === "current" ? "1.2rem" : "1rem",
|
||||||
|
color: "black",
|
||||||
|
visibility: "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.role}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* Навигационные точки */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "relative",
|
||||||
|
bottom: "-5vw",
|
||||||
|
top: "50%",
|
||||||
|
display: "flex",
|
||||||
|
gap: "1vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{teamMembers.map((_, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleMemberClick(index)}
|
||||||
|
sx={{
|
||||||
|
width: isMobile ? "3vw" : "1vw",
|
||||||
|
height: isMobile ? "3vw" : "1vw",
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: index === activeIndex ? "#C27664" : "#CDCDCD",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "4vw" : "1.5rem",
|
||||||
|
textAlign: "center",
|
||||||
|
my: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Информация о команде скоро появится
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TeamPage;
|
125
src/utils/api.ts
125
src/utils/api.ts
@ -1,10 +1,9 @@
|
|||||||
// utils/api.ts
|
|
||||||
|
|
||||||
// Типы данных
|
|
||||||
|
// Типы данных для автомобилей
|
||||||
export interface Car {
|
export interface Car {
|
||||||
id: number;
|
id: number;
|
||||||
make: string;
|
name: string;
|
||||||
model: string;
|
|
||||||
year: number;
|
year: number;
|
||||||
mileage: number;
|
mileage: number;
|
||||||
price: number;
|
price: number;
|
||||||
@ -20,6 +19,24 @@ export interface CarResponse {
|
|||||||
car: Car;
|
car: Car;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Типы данных для персонала
|
||||||
|
export interface TeamMember {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
role: string;
|
||||||
|
photo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TeamResponse {
|
||||||
|
staff: TeamMember[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TeamMemberResponse {
|
||||||
|
personal: TeamMember;
|
||||||
|
}
|
||||||
|
|
||||||
// Базовый URL API
|
// Базовый URL API
|
||||||
const API_BASE_URL = 'http://localhost:8000';
|
const API_BASE_URL = 'http://localhost:8000';
|
||||||
|
|
||||||
@ -129,3 +146,103 @@ export const getImageUrl = (imagePath: string | null): string => {
|
|||||||
if (!imagePath) return '/placeholder.jpg';
|
if (!imagePath) return '/placeholder.jpg';
|
||||||
return `${API_BASE_URL}${imagePath}`;
|
return `${API_BASE_URL}${imagePath}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ФУНКЦИИ ДЛЯ РАБОТЫ С ПЕРСОНАЛОМ
|
||||||
|
|
||||||
|
// Получение списка сотрудников
|
||||||
|
export const fetchTeam = async (skip = 0, limit = 100): Promise<TeamResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/personal?skip=${skip}&limit=${limit}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Получение информации о сотруднике по ID
|
||||||
|
export const fetchTeamMemberById = async (memberId: number): Promise<TeamMemberResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/personal/${memberId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Добавление нового сотрудника
|
||||||
|
export const createTeamMember = async (
|
||||||
|
memberData: Omit<TeamMember, 'id'>,
|
||||||
|
photo?: File
|
||||||
|
): Promise<TeamMemberResponse> => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('personal_data', JSON.stringify(memberData));
|
||||||
|
|
||||||
|
if (photo) {
|
||||||
|
formData.append('photo', photo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/personal`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обновление информации о сотруднике
|
||||||
|
export const updateTeamMember = async (
|
||||||
|
memberId: number,
|
||||||
|
memberData: Partial<Omit<TeamMember, 'id'>>,
|
||||||
|
photo?: File
|
||||||
|
): Promise<TeamMemberResponse> => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('personal_data', JSON.stringify(memberData));
|
||||||
|
|
||||||
|
if (photo) {
|
||||||
|
formData.append('photo', photo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/personal/${memberId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Удаление сотрудника
|
||||||
|
export const deleteTeamMember = async (memberId: number): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/personal/${memberId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -9,6 +9,9 @@ export const scrollToAnchor = (
|
|||||||
setDrawerOpen?: (isOpen: boolean) => void,
|
setDrawerOpen?: (isOpen: boolean) => void,
|
||||||
isMobile?: boolean
|
isMobile?: boolean
|
||||||
): void => {
|
): void => {
|
||||||
|
if (location.pathname.startsWith("/car")) {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
const element = document.querySelector(anchor);
|
const element = document.querySelector(anchor);
|
||||||
if (element) {
|
if (element) {
|
||||||
// Получаем высоту хедера (в соответствии с размерами в Header.tsx)
|
// Получаем высоту хедера (в соответствии с размерами в Header.tsx)
|
||||||
|
Reference in New Issue
Block a user