diff --git a/src/App.tsx b/src/App.tsx
index 145ca18..b16dc74 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,6 @@
import { Routes, Route } from 'react-router-dom'
import MainPage from './pages/MainPage.tsx'
+import CarPage from './pages/CarPage.tsx'
import Header from './components/Header.tsx'
function App() {
@@ -15,6 +16,10 @@ function App() {
>
}
/>
+ }
+ />
>
)
diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx
index 512e68d..bdb72ae 100644
--- a/src/components/Divider.tsx
+++ b/src/components/Divider.tsx
@@ -2,10 +2,15 @@ import { Box } from "@mui/material";
import React from "react";
import { useResponsive } from "../theme/useResponsive";
-function Divider() {
+interface DividerProps {
+ marginTopDivider?: string | "1vw";
+ marginBottomDivider?: string | "1vw";
+}
+
+function Divider({ marginTopDivider, marginBottomDivider }: DividerProps) {
const { isMobile } = useResponsive();
return (
-
+
diff --git a/src/components/Feedback.tsx b/src/components/Feedback.tsx
index c0c665f..a915291 100644
--- a/src/components/Feedback.tsx
+++ b/src/components/Feedback.tsx
@@ -62,7 +62,7 @@ const Feedback: React.FC = ({
}) => {
const [name, setName] = useState("");
const [phone, setPhone] = useState("+7");
- const [country, setCountry] = useState("Европа");
+ const [country, setCountry] = useState("США");
const [budget, setBudget] = useState("до 3 млн");
const [description, setDescription] = useState("");
const [agreeToPolicy, setAgreeToPolicy] = useState(false);
@@ -271,35 +271,6 @@ const Feedback: React.FC = ({
value={country}
onChange={(e) => setCountry(e.target.value)}
>
-
- }
- label={
-
- Европа
-
- }
- sx={{
- marginRight: isMobile ? "2.5vw" : "1.5vw",
- ml: "0vw",
- }}
- />
{
const { isMobile } = useResponsive();
const [drawerOpen, setDrawerOpen] = useState(false);
const [feedbackOpen, setFeedbackOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
+ const navigate = useNavigate();
const menuItems = [
+ // { title: "", anchor: "#main" },
{ title: "О нас", anchor: "#about-us" },
+ { title: "Этапы работы", anchor: "#stages" },
+ { title: "В наличии", anchor: "#available" },
{ title: "Калькулятор", anchor: "#calculator" },
+ { title: "Команда", anchor: "#team" },
{ title: "Отзывы", anchor: "#reviews" },
{ title: "Контакты", anchor: "#contacts" },
- { title: "В наличии", anchor: "#available" },
- { title: "Команда", anchor: "#team" },
- { title: "Доставленные авто", anchor: "#delivered" },
- { title: "Этапы работы", anchor: "#stages" },
- { title: " ", anchor: "#main" },
+ // { title: "Доставленные авто", anchor: "#delivered" },
];
// Отслеживание скролла
@@ -62,7 +65,11 @@ const Header = () => {
// Используем глобальную функцию из utils
const handleScrollToAnchor = (anchor: string) => {
- scrollToAnchor(anchor, setDrawerOpen, isMobile);
+ if (location.pathname.startsWith("/car")) {
+ navigate("/");
+ } else {
+ scrollToAnchor(anchor, setDrawerOpen, isMobile);
+ }
};
return (
@@ -153,7 +160,7 @@ const Header = () => {
{/* Пустой блок для компенсации фиксированного хедера */}
-
+
{/* Мобильное меню */}
diff --git a/src/pages/CalculatorPage.tsx b/src/pages/CalculatorPage.tsx
index 3c7aa9a..e2f2b43 100644
--- a/src/pages/CalculatorPage.tsx
+++ b/src/pages/CalculatorPage.tsx
@@ -13,6 +13,7 @@ import {
Collapse,
Paper,
Slide,
+ Checkbox,
} from "@mui/material";
import { useResponsive } from "../theme/useResponsive";
import russia from "../assets/emoji/russia.png";
@@ -330,7 +331,6 @@ function CalculatorPage() {
scrollMarginTop: isMobile ? "15vw" : "10vw",
maxWidth: "100vw",
overflowX: "hidden",
- pb: "25vw",
}}
>
setEnhancedPermeability(!enhancedPermeability)
diff --git a/src/pages/CarPage.tsx b/src/pages/CarPage.tsx
new file mode 100644
index 0000000..9d52535
--- /dev/null
+++ b/src/pages/CarPage.tsx
@@ -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(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 (
+
+
+
+ );
+ }
+
+ if (!car) {
+ return (
+
+
+ Автомобиль не найден
+
+
+ Запрошенный автомобиль не существует или был удален.
+
+
+ );
+ }
+
+ console.log("Отрисовка автомобиля:", car);
+ console.log("Тип двигателя:", car.engine_type);
+ console.log("Объем двигателя:", car.engine_capacity);
+ console.log("Является электрическим:", isElectric);
+
+ return (
+
+
+ {/* Название автомобиля */}
+ {/*
+ Автомобиль - {car.name}
+ */}
+
+
+ {/* Изображение автомобиля */}
+
+
+
+
+
+ {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)}.`}
+
+
+
+
+ {/* Информация об автомобиле */}
+
+
+
+
+ {car.price.toLocaleString()}₽
+
+
+
+ Год выпуска: {car.year}
+
+
+ Пробег: {car.mileage.toLocaleString()} км
+
+
+ {!isElectric && car.engine_capacity !== null && car.engine_capacity !== undefined && (
+
+ Объём двигателя: {car.engine_capacity} л
+
+ )}
+
+ {car.engine_power && isElectric && (
+
+ Мощность: {Math.round(car.engine_power * 0.735499)} кВт ({car.engine_power} л.с)
+
+ )}
+
+ {car.engine_power && !isElectric && (
+
+ Мощность: {car.engine_power} л.с.
+
+ )}
+
+ {car.engine_type && (
+
+ Тип двигателя: {car.engine_type}
+
+ )}
+
+ {car.drive_type && (
+
+ Привод: {translateWheelDrive(car.drive_type)}
+
+ )}
+
+ {car.country_of_origin && (
+
+ Страна производства: {translateCountry(car.country_of_origin)}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ window.open("https://t.me/autoBROcn", "_blank")}
+ sx={{
+ color: "#C27664",
+ "&:hover": { color: "#945B4D" },
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Дополнительная информация */}
+
+
+ {car.name}
+
+
+
+
+
+
+ );
+}
+
+export default CarPage;
\ No newline at end of file
diff --git a/src/pages/ContactsPage.tsx b/src/pages/ContactsPage.tsx
new file mode 100644
index 0000000..467fbd0
--- /dev/null
+++ b/src/pages/ContactsPage.tsx
@@ -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 (
+
+
+
+ НАШИ КОНТАКТЫ
+
+
+
+
+
+
+ 8 (965) 372-51-90
+
+
+
+
+ info@autobro.ru
+
+
+
+
+
+ г.Москва, станция метро "Домодедово"
+
+
+
+
+
+ Мы в соцсетях:
+
+
+
+
+ window.open("https://t.me/autoBROcn", "_blank")}
+ sx={socialIconStyle}
+ >
+
+
+
+
+
+
+
+
+
+
+ Остались вопросы?
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ContactsPage;
\ No newline at end of file
diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx
index f1b4855..662ba12 100644
--- a/src/pages/MainPage.tsx
+++ b/src/pages/MainPage.tsx
@@ -13,7 +13,8 @@ import CarsPage from "./CarsPage";
import DeliveryPage from "./DeliveryPage";
import Divider from "../components/Divider";
import CalculatorPage from "./CalculatorPage";
-
+import TeamPage from "./TeamPage";
+import ContactsPage from "./ContactsPage";
function MainPage() {
const [feedbackOpen, setFeedbackOpen] = useState(false);
const setDrawerOpen = () => {};
@@ -155,7 +156,6 @@ function MainPage() {
+
+
+
+
);
}
diff --git a/src/pages/TeamPage.tsx b/src/pages/TeamPage.tsx
new file mode 100644
index 0000000..fae38fc
--- /dev/null
+++ b/src/pages/TeamPage.tsx
@@ -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([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(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 (
+
+
+
+ Наша команда
+
+
+ {loading ? (
+
+
+
+ ) : error ? (
+
+ {error}
+
+ ) : teamMembers.length > 0 ? (
+
+
+ {/* Карусель с сотрудниками */}
+ {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 (
+ position !== "current" && handleMemberClick(index)}
+ style={{
+ position: "absolute",
+ left: positionStyles[position].left,
+ top: positionStyles[position].top,
+ cursor: position !== "current" ? "pointer" : "default",
+ }}
+ >
+
+
+
+
+
+
+
+
+ {member.name} {member.surname}
+
+
+ {member.role}
+
+
+
+ );
+ })}
+
+
+ {/* Навигационные точки */}
+
+ {teamMembers.map((_, index) => (
+ handleMemberClick(index)}
+ sx={{
+ width: isMobile ? "3vw" : "1vw",
+ height: isMobile ? "3vw" : "1vw",
+ borderRadius: "50%",
+ backgroundColor: index === activeIndex ? "#C27664" : "#CDCDCD",
+ cursor: "pointer",
+ }}
+ />
+ ))}
+
+
+ ) : (
+
+ Информация о команде скоро появится
+
+ )}
+
+
+ );
+}
+
+export default TeamPage;
\ No newline at end of file
diff --git a/src/utils/api.ts b/src/utils/api.ts
index dc84700..6637113 100644
--- a/src/utils/api.ts
+++ b/src/utils/api.ts
@@ -1,10 +1,9 @@
-// utils/api.ts
-// Типы данных
+
+// Типы данных для автомобилей
export interface Car {
id: number;
- make: string;
- model: string;
+ name: string;
year: number;
mileage: number;
price: number;
@@ -20,6 +19,24 @@ export interface CarResponse {
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
const API_BASE_URL = 'http://localhost:8000';
@@ -129,3 +146,103 @@ export const getImageUrl = (imagePath: string | null): string => {
if (!imagePath) return '/placeholder.jpg';
return `${API_BASE_URL}${imagePath}`;
};
+
+// ФУНКЦИИ ДЛЯ РАБОТЫ С ПЕРСОНАЛОМ
+
+// Получение списка сотрудников
+export const fetchTeam = async (skip = 0, limit = 100): Promise => {
+ 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 => {
+ 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,
+ photo?: File
+): Promise => {
+ 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>,
+ photo?: File
+): Promise => {
+ 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 => {
+ 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);
+ }
+};
diff --git a/src/utils/scrollUtils.ts b/src/utils/scrollUtils.ts
index 9431e61..9be1128 100644
--- a/src/utils/scrollUtils.ts
+++ b/src/utils/scrollUtils.ts
@@ -9,6 +9,9 @@ export const scrollToAnchor = (
setDrawerOpen?: (isOpen: boolean) => void,
isMobile?: boolean
): void => {
+ if (location.pathname.startsWith("/car")) {
+ navigate("/");
+ }
const element = document.querySelector(anchor);
if (element) {
// Получаем высоту хедера (в соответствии с размерами в Header.tsx)