This commit is contained in:
Joe_Peach committed 2026-05-22 21:17:36 +03:00
1 parent 0c927a57f6
commit b8422f5472
8 files changed
+560 -14

No files matched your search

+7 -1
View File
@@ -16,6 +16,7 @@
{ {
"id": 1, "id": 1,
"name": "цвет", "name": "цвет",
"flag": "color",
"vals": [ "vals": [
{ "id": 1001, "val": "оранжевый" }, { "id": 1001, "val": "оранжевый" },
{ "id": 1002, "val": "серый" }, { "id": 1002, "val": "серый" },
@@ -24,6 +25,7 @@
{ {
"id": 2, "id": 2,
"name": "размер", "name": "размер",
"flag": "size",
"vals": [ "vals": [
{ "id": 2001, "val": "..." }, { "id": 2001, "val": "..." },
{ "id": 2002, "val": "...." }, { "id": 2002, "val": "...." },
@@ -33,6 +35,7 @@
{ {
"id": 2, "id": 2,
"name": "кол-во в упаковке", "name": "кол-во в упаковке",
"flag": "хз какой то флаг",
"vals": [ "vals": [
{ "id": 2001, "val": "2" }, { "id": 2001, "val": "2" },
{ "id": 2002, "val": "4" }, { "id": 2002, "val": "4" },
@@ -48,6 +51,7 @@
{ {
"id": 1, "id": 1,
"name": "цвет", "name": "цвет",
"flag": "color",
"vals": [ "vals": [
{ "id": 1001, "val": "оранжевый" }, { "id": 1001, "val": "оранжевый" },
{ "id": 1002, "val": "серый" }, { "id": 1002, "val": "серый" },
@@ -56,6 +60,7 @@
{ {
"id": 2, "id": 2,
"name": "размер", "name": "размер",
"flag": "size",
"vals": [ "vals": [
{ "id": 2001, "val": "..." }, { "id": 2001, "val": "..." },
{ "id": 2002, "val": "...." }, { "id": 2002, "val": "...." },
@@ -65,6 +70,7 @@
{ {
"id": 2, "id": 2,
"name": "кол-во в упаковке", "name": "кол-во в упаковке",
"flag": "хз какой то флаг",
"vals": [ "vals": [
{ "id": 2001, "val": "2" }, { "id": 2001, "val": "2" },
{ "id": 2002, "val": "4" }, { "id": 2002, "val": "4" },
@@ -158,7 +164,7 @@ GET /api/products/101
### Получение списка товаров ### Получение списка товаров
**Запрос** **Запрос**
``` ```
GET api/productList/ GET api/productList/
``` ```
+2 -2
View File
@@ -18,7 +18,7 @@ const categories: CategoryItem[] = [
{ {
id: 1, id: 1,
title: "Труба ПВХ", title: "Труба ПВХ",
subtitle: "для ВНУТРЕННЕЙ канализации", subtitle: "для НАРУЖНОЙ канализации",
image: category_interna2, image: category_interna2,
highlight: true, highlight: true,
}, },
@@ -31,7 +31,7 @@ highlight: true,
{ {
id: 2, id: 2,
title: "Труба ПВХ", title: "Труба ПВХ",
subtitle: "для НАРУЖНОЙ канализации", subtitle: "для ВНУТРЕННЕЙ канализации",
highlight: true, highlight: true,
image: category_internal1, image: category_internal1,
}, },
+6 -2
View File
@@ -2,20 +2,23 @@ import React from "react";
import styles from "./Footer.module.css"; import styles from "./Footer.module.css";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import blackLogo from "../../assets/images/BlackLogo.jpg"; import blackLogo from "../../assets/images/BlackLogo.jpg";
import PriceLisForm from "../PriceLisForm/PriceLisForm";
import { useState } from "react";
const Footer: React.FC = () => { const Footer: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
return ( return (
<footer className={styles.footer}> <footer className={styles.footer}>
<div className={styles.container}> <div className={styles.container}>
<div className={styles.left}> <div className={styles.left}>
<img src={blackLogo} alt="logo" className={styles.logo} /> <img src={blackLogo} alt="logo" className={styles.logo} />
<span className={styles.copy}>РК Plastic. 2015-2026 ©</span> <span className={styles.copy}>RK-Plastic Tech. 2021-2026 ©</span>
</div> </div>
<div className={styles.center}> <div className={styles.center}>
<NavLink to="about">O КОМПАНИИ</NavLink> <NavLink to="about">O КОМПАНИИ</NavLink>
<NavLink to="products">ПРОДУКЦИЯ</NavLink> <NavLink to="products">ПРОДУКЦИЯ</NavLink>
<NavLink to="products">ПРАЙС-ЛИСТ</NavLink> <NavLink to="products" onClick={() => setIsOpen(true)}>ПРАЙС-ЛИСТ</NavLink>
</div> </div>
<div className={styles.right}> <div className={styles.right}>
@@ -23,6 +26,7 @@ const Footer: React.FC = () => {
<a className={styles.mail}>rkplastic@mail.ru</a> <a className={styles.mail}>rkplastic@mail.ru</a>
</div> </div>
</div> </div>
<PriceLisForm open={isOpen} onClose={() => setIsOpen(false)} />
</footer> </footer>
); );
}; };
@@ -1,4 +1,4 @@
import React from "react"; import React, { useState } from "react";
import { Modal } from "antd"; import { Modal } from "antd";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import InputMask from "react-input-mask"; import InputMask from "react-input-mask";
@@ -30,6 +30,8 @@ interface FormData {
} }
const ModalRequestForm: React.FC<Props> = ({ open, onClose }) => { const ModalRequestForm: React.FC<Props> = ({ open, onClose }) => {
const [isOpen, setIsOpen] = useState(false);
console.log('ModalRequestForm render, open:', open); console.log('ModalRequestForm render, open:', open);
const { const {
register, register,
@@ -93,6 +95,7 @@ const ModalRequestForm: React.FC<Props> = ({ open, onClose }) => {
reset(); reset();
onClose(); onClose();
setIsOpen(true);
} catch (error) { } catch (error) {
// Показываем ошибку // Показываем ошибку
Modal.error({ Modal.error({
@@ -104,6 +107,25 @@ const ModalRequestForm: React.FC<Props> = ({ open, onClose }) => {
}; };
return ( return (
<>
<Modal
open={isOpen}
footer={null}
onCancel={() => setIsOpen(false)}
centered
width={500}
>
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35 }}
className={styles.modalBox}
>
<p className={styles.title}>
Спасибо за обращение! Мы свяжемся с Вами в ближайшее время. Удачного дня!
</p>
</motion.div>
</Modal>
<Modal <Modal
open={open} open={open}
footer={null} footer={null}
@@ -185,6 +207,7 @@ const ModalRequestForm: React.FC<Props> = ({ open, onClose }) => {
</form> </form>
</motion.div> </motion.div>
</Modal> </Modal>
</>
); );
}; };
@@ -7,6 +7,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
import styles from "./PriceLisForm.module.css"; import styles from "./PriceLisForm.module.css";
import Button from "../Button/Button"; import Button from "../Button/Button";
import { client } from "../../api/client"; import { client } from "../../api/client";
import { useState } from "react";
// ✅ Схема валидации // ✅ Схема валидации
const schema = yup.object().shape({ const schema = yup.object().shape({
@@ -27,6 +28,7 @@ interface FormData {
} }
const PriceLisForm: React.FC<Props> = ({ open, onClose }) => { const PriceLisForm: React.FC<Props> = ({ open, onClose }) => {
const [isOpen, setIsOpen] = useState(false);
console.log('ModalRequestForm render, open:', open); console.log('ModalRequestForm render, open:', open);
const { const {
register, register,
@@ -85,6 +87,7 @@ const PriceLisForm: React.FC<Props> = ({ open, onClose }) => {
reset(); reset();
onClose(); onClose();
setIsOpen(true);
} catch (error) { } catch (error) {
Modal.error({ Modal.error({
title: "Ошибка", title: "Ошибка",
@@ -95,6 +98,25 @@ const PriceLisForm: React.FC<Props> = ({ open, onClose }) => {
}; };
return ( return (
<>
<Modal
open={isOpen}
footer={null}
onCancel={() => setIsOpen(false)}
centered
width={500}
>
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35 }}
className={styles.modalBox}
>
<p className={styles.title}>
Спасибо за обращение! Мы свяжемся с Вами в ближайшее время. Удачного дня!
</p>
</motion.div>
</Modal>
<Modal <Modal
open={open} open={open}
footer={null} footer={null}
@@ -154,6 +176,7 @@ const PriceLisForm: React.FC<Props> = ({ open, onClose }) => {
</form> </form>
</motion.div> </motion.div>
</Modal> </Modal>
</>
); );
}; };
+23
View File
@@ -8,6 +8,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
import styles from "../../components/ModalRequestForm/ModalRequestForm.module.css"; import styles from "../../components/ModalRequestForm/ModalRequestForm.module.css";
import Button from "../../components/Button/Button"; import Button from "../../components/Button/Button";
import { client } from "../../api/client"; import { client } from "../../api/client";
import { useState } from "react";
// ✅ Схема валидации // ✅ Схема валидации
const schema = yup.object().shape({ const schema = yup.object().shape({
@@ -30,6 +31,7 @@ interface FormData {
} }
const ModalRequestFormQuastion: React.FC<Props> = ({ open, onClose }) => { const ModalRequestFormQuastion: React.FC<Props> = ({ open, onClose }) => {
const [isOpen, setIsOpen] = useState(false);
console.log('ModalRequestForm render, open:', open); console.log('ModalRequestForm render, open:', open);
const { const {
register, register,
@@ -93,6 +95,7 @@ const ModalRequestFormQuastion: React.FC<Props> = ({ open, onClose }) => {
reset(); reset();
onClose(); onClose();
setIsOpen(true)
} catch (error) { } catch (error) {
// Показываем ошибку // Показываем ошибку
Modal.error({ Modal.error({
@@ -104,6 +107,25 @@ const ModalRequestFormQuastion: React.FC<Props> = ({ open, onClose }) => {
}; };
return ( return (
<>
<Modal
open={isOpen}
footer={null}
onCancel={() => setIsOpen(false)}
centered
width={500}
>
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35 }}
className={styles.modalBox}
>
<p className={styles.title}>
Спасибо за обращение! Мы свяжемся с Вами в ближайшее время. Удачного дня!
</p>
</motion.div>
</Modal>
<Modal <Modal
open={open} open={open}
footer={null} footer={null}
@@ -185,6 +207,7 @@ const ModalRequestFormQuastion: React.FC<Props> = ({ open, onClose }) => {
</form> </form>
</motion.div> </motion.div>
</Modal> </Modal>
</>
); );
}; };
+327
View File
@@ -0,0 +1,327 @@
.page {
width: 100%;
background: #fff;
color: #151515;
padding: 126px 20px 96px;
box-sizing: border-box;
}
.hero,
.facts,
.story,
.principles,
.production {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(360px, 0.92fr);
gap: 52px;
align-items: center;
}
.heroContent {
min-width: 0;
}
.kicker,
.sectionLabel {
display: inline-block;
color: var(--lightblue);
font-size: 14px;
font-weight: 700;
letter-spacing: 0.08em;
margin-bottom: 16px;
text-transform: uppercase;
}
.hero h1 {
max-width: 760px;
font-size: clamp(38px, 5vw, 68px);
line-height: 1.05;
margin: 0;
color: #111;
}
.hero p {
max-width: 650px;
margin: 28px 0 0;
font-size: 19px;
line-height: 1.7;
color: #3f4652;
}
.heroActions {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-top: 36px;
}
.primaryLink,
.secondaryLink {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 52px;
padding: 0 28px;
border-radius: 50px;
font-size: 15px;
font-weight: 700;
text-decoration: none;
transition: 0.25s ease;
}
.primaryLink {
background: #ffd400;
color: #000;
}
.primaryLink:hover {
background: #ffe667;
transform: translateY(-2px);
}
.secondaryLink {
border: 1px solid #d7dce3;
color: #111;
background: #fff;
}
.secondaryLink:hover {
border-color: var(--lightblue);
color: var(--darkblue);
}
.heroImageWrap {
position: relative;
overflow: hidden;
min-height: 520px;
border-radius: 8px;
background: #edf1f6;
}
.heroImage {
width: 100%;
height: 100%;
min-height: 520px;
display: block;
object-fit: cover;
}
.facts {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
margin-top: 56px;
}
.fact {
padding: 28px;
border: 1px solid #e7ebf0;
border-radius: 8px;
background: #f8fafc;
}
.fact strong {
display: block;
font-size: 28px;
line-height: 1.15;
color: var(--darkblue);
margin-bottom: 8px;
}
.fact span {
color: #4f5966;
font-size: 16px;
line-height: 1.5;
}
.story {
display: grid;
grid-template-columns: 0.8fr 1.2fr;
gap: 60px;
margin-top: 92px;
align-items: start;
}
.story h2,
.production h2 {
margin: 0;
color: #151515;
font-size: clamp(30px, 4vw, 46px);
line-height: 1.16;
}
.storyText {
display: grid;
gap: 20px;
}
.storyText p,
.productionText p {
margin: 0;
color: #3f4652;
font-size: 18px;
line-height: 1.75;
}
.principles {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 22px;
margin-top: 56px;
}
.principle {
padding: 30px;
border-radius: 8px;
border: 1px solid #e7ebf0;
background: #fff;
box-shadow: 0 18px 50px rgba(17, 31, 51, 0.06);
}
.principleIcon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
color: var(--darkblue);
background: #eef7ff;
font-size: 24px;
margin-bottom: 24px;
}
.principle h3 {
margin: 0 0 12px;
color: #151515;
font-size: 22px;
}
.principle p {
margin: 0;
color: #4f5966;
font-size: 16px;
line-height: 1.65;
}
.production {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(340px, 0.8fr);
gap: 48px;
align-items: center;
margin-top: 96px;
padding: 46px;
border-radius: 8px;
background: linear-gradient(135deg, #f7fbff 0%, #ffffff 58%, #fff7cf 100%);
border: 1px solid #e7ebf0;
}
.productionText p {
margin-top: 22px;
}
.checkList {
display: grid;
gap: 18px;
padding: 0;
margin: 0;
list-style: none;
}
.checkList li {
display: flex;
gap: 12px;
align-items: flex-start;
color: #202733;
font-size: 17px;
line-height: 1.5;
}
.checkList svg {
flex: 0 0 auto;
color: var(--lightblue);
font-size: 20px;
margin-top: 3px;
}
@media (max-width: 980px) {
.page {
padding-top: 104px;
}
.hero,
.story,
.production {
grid-template-columns: 1fr;
}
.heroImageWrap,
.heroImage {
min-height: 360px;
}
.facts,
.principles {
grid-template-columns: 1fr;
}
.story {
gap: 28px;
margin-top: 72px;
}
.production {
padding: 34px 26px;
}
}
@media (max-width: 560px) {
.page {
padding: 92px 16px 72px;
}
.hero {
gap: 34px;
}
.hero h1 {
font-size: 34px;
}
.hero p,
.storyText p,
.productionText p {
font-size: 16px;
}
.heroActions {
flex-direction: column;
}
.primaryLink,
.secondaryLink {
width: 100%;
box-sizing: border-box;
}
.heroImageWrap,
.heroImage {
min-height: 280px;
}
.fact,
.principle {
padding: 24px;
}
.production {
margin-top: 72px;
padding: 28px 22px;
}
}
+148 -8
View File
@@ -1,9 +1,149 @@
const About = () =>{ import { Link } from "react-router-dom";
return( import {
<> CheckCircleOutlined,
<p>gggggggg</p> ExperimentOutlined,
</> SafetyCertificateOutlined,
) ToolOutlined,
} } from "@ant-design/icons";
import styles from "./About.module.css";
import SEO from "../../shared/ui/SEO";
import productionImage from "../../assets/images/otkrytie-novogo-proizvodstva-v-g.Krymsk.jpeg";
export default About; const facts = [
{ value: "Собственное", label: "производство и техническая база" },
{ value: "Контроль", label: "сырья, геометрии и качества изделий" },
{ value: "Партнерство", label: "с монтажными, торговыми и строительными компаниями" },
];
const principles = [
{
icon: <SafetyCertificateOutlined />,
title: "Надежность",
text: "Выпускаем продукцию для инженерных систем, где важны стабильные характеристики, аккуратная геометрия и понятная комплектация.",
},
{
icon: <ExperimentOutlined />,
title: "Контроль качества",
text: "Следим за производственными этапами, проверяем параметры изделий и бережно относимся к повторяемости каждой партии.",
},
{
icon: <ToolOutlined />,
title: "Гибкость",
text: "Помогаем подобрать подходящие позиции под объект, объем закупки и требования проекта, включая контрактное производство.",
},
];
const About = () => {
return (
<>
<SEO
title="О компании"
description="Информация о производственной компании: выпуск ПВХ-продукции, контроль качества, контрактное производство и сотрудничество с партнерами."
/>
<main className={styles.page}>
<section className={styles.hero}>
<div className={styles.heroContent}>
<span className={styles.kicker}>О компании</span>
<h1>Производим решения для инженерных и строительных задач</h1>
<p>
Мы развиваем современное производство ПВХ-продукции и комплектующих
для объектов, где важны надежность поставок, понятное качество и
внимательная работа с заказчиком.
</p>
<div className={styles.heroActions}>
<Link to="/products" className={styles.primaryLink}>
Смотреть продукцию
</Link>
<a href="tel:+79101210962" className={styles.secondaryLink}>
+7 910 121 09 62
</a>
</div>
</div>
<div className={styles.heroImageWrap}>
<img
src={productionImage}
alt="Производственная площадка компании"
className={styles.heroImage}
/>
</div>
</section>
<section className={styles.facts} aria-label="Ключевые факты">
{facts.map((fact) => (
<div className={styles.fact} key={fact.value}>
<strong>{fact.value}</strong>
<span>{fact.label}</span>
</div>
))}
</section>
<section className={styles.story}>
<div>
<span className={styles.sectionLabel}>Кто мы</span>
<h2>Компания, которая держит фокус на производстве и результате</h2>
</div>
<div className={styles.storyText}>
<p>
Наша команда работает с продукцией для водоснабжения, канализации,
монтажа и смежных инженерных направлений. Мы понимаем, что для
клиентов важны не только цена и наличие, но и предсказуемость:
сроки, качество партии, корректная консультация и спокойная
коммуникация на каждом этапе заказа.
</p>
<p>
Поэтому мы выстраиваем процессы вокруг практических задач клиента:
помогаем с подбором номенклатуры, готовим предложения под объем,
поддерживаем партнеров в регулярных закупках и берем в работу
запросы на контрактное производство.
</p>
</div>
</section>
<section className={styles.principles}>
{principles.map((item) => (
<article className={styles.principle} key={item.title}>
<div className={styles.principleIcon}>{item.icon}</div>
<h3>{item.title}</h3>
<p>{item.text}</p>
</article>
))}
</section>
<section className={styles.production}>
<div className={styles.productionText}>
<span className={styles.sectionLabel}>Производство</span>
<h2>От сырья до готовой партии</h2>
<p>
Мы уделяем внимание стабильности технологического процесса,
состоянию оборудования и качеству готовых изделий. Такой подход
помогает сохранять повторяемость продукции и уверенно работать как
с разовыми заказами, так и с постоянными поставками.
</p>
</div>
<ul className={styles.checkList}>
<li>
<CheckCircleOutlined />
Подбираем материалы под назначение продукции
</li>
<li>
<CheckCircleOutlined />
Контролируем размеры и внешний вид изделий
</li>
<li>
<CheckCircleOutlined />
Упаковываем продукцию с учетом хранения и доставки
</li>
<li>
<CheckCircleOutlined />
Работаем с оптовыми, проектными и партнерскими заказами
</li>
</ul>
</section>
</main>
</>
);
};
export default About;