Соединение бек и фронт
This commit is contained in:
parent
942c028945
commit
5689c48c01
@ -8,12 +8,13 @@ import io
|
|||||||
import joblib
|
import joblib
|
||||||
from flask import Flask, request, jsonify, Blueprint, send_file
|
from flask import Flask, request, jsonify, Blueprint, send_file
|
||||||
from flasgger import Swagger
|
from flasgger import Swagger
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
api = Blueprint('api', __name__)
|
api = Blueprint('api', __name__)
|
||||||
Swagger(app)
|
Swagger(app)
|
||||||
|
CORS(app)
|
||||||
# Загружаем модель и scaler
|
# Загружаем модель и scaler
|
||||||
model = load_model("my_model_1H.keras")
|
model = load_model("my_model_1H.keras")
|
||||||
scaler = MinMaxScaler(feature_range=(0, 1))
|
scaler = MinMaxScaler(feature_range=(0, 1))
|
||||||
|
@ -16,11 +16,13 @@ function HomePage() {
|
|||||||
const [marketplaces, setMarketplaces] = useState([]);
|
const [marketplaces, setMarketplaces] = useState([]);
|
||||||
const [productUrl, setProductUrl] = useState('');
|
const [productUrl, setProductUrl] = useState('');
|
||||||
const [showPriceHistoryError, setShowPriceHistoryError] = useState(false);
|
const [showPriceHistoryError, setShowPriceHistoryError] = useState(false);
|
||||||
|
const [showResultError, setShowResultError] = useState(false);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMarketplaces = async () => {
|
const fetchMarketplaces = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/api/v1/marketplaces');
|
const response = await axios.get('https://mgpj3mxm-8080.euw.devtunnels.ms/api/v1/marketplaces');
|
||||||
setMarketplaces(response.data);
|
setMarketplaces(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching marketplaces:', error);
|
console.error('Error fetching marketplaces:', error);
|
||||||
@ -34,7 +36,8 @@ function HomePage() {
|
|||||||
const fetchCategories = async () => {
|
const fetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
if (selectedMarketplace) {
|
if (selectedMarketplace) {
|
||||||
const response = await axios.get(`/api/v1/categories?marketplace=${selectedMarketplace}`);
|
console.log(selectedMarketplace)
|
||||||
|
const response = await axios.get(`https://mgpj3mxm-8080.euw.devtunnels.ms/api/v1/categories?marketplace=${selectedMarketplace}`);
|
||||||
setCategories(response.data);
|
setCategories(response.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -46,10 +49,17 @@ function HomePage() {
|
|||||||
}, [selectedMarketplace]);
|
}, [selectedMarketplace]);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
console.log('Отправлено:', startDate, endDate, selectedMarketplace);
|
if (selectedCategory !== "" || selectedMarketplace !== "") {
|
||||||
navigate('/result', {
|
navigate('/result', {
|
||||||
state: { startDate, endDate, selectedMarketplace }
|
state: { startDate, endDate, selectedMarketplace, selectedCategory }
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setShowResultError(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCategoryChange = (event) => {
|
||||||
|
setSelectedCategory(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProductUrlChange = (event) => {
|
const handleProductUrlChange = (event) => {
|
||||||
@ -64,7 +74,8 @@ function HomePage() {
|
|||||||
|
|
||||||
// Проверка существования товара по ссылке
|
// Проверка существования товара по ссылке
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/products/info?productUrl=${productUrl}`);
|
const response = await fetch(`https://mgpj3mxm-8080.euw.devtunnels.ms/api/v1/products/info?productUrl=${productUrl}`);
|
||||||
|
console.log(response.data);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// Товар найден
|
// Товар найден
|
||||||
navigate('/viewProduct', { state: { productUrl } });
|
navigate('/viewProduct', { state: { productUrl } });
|
||||||
@ -83,6 +94,7 @@ function HomePage() {
|
|||||||
setSelectedMarketplace('');
|
setSelectedMarketplace('');
|
||||||
} else {
|
} else {
|
||||||
setSelectedMarketplace(marketplaceName);
|
setSelectedMarketplace(marketplaceName);
|
||||||
|
setSelectedCategory('');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -146,7 +158,7 @@ function HomePage() {
|
|||||||
height: '90%',
|
height: '90%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h1" gutterBottom style={{ color: '#132a52', marginBottom: '1.5rem', fontWeight: 'bold' }}>
|
<Typography variant="h1" gutterBottom style={{ color: '#132a52', marginBottom: '1.5rem', fontWeight: 'bold' }}>
|
||||||
@ -165,15 +177,17 @@ function HomePage() {
|
|||||||
Выберите маркетплейсы, которые вас интересуют:
|
Выберите маркетплейсы, которые вас интересуют:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{marketplaces.map((marketplace) => (
|
{marketplaces.map((marketplace) => {
|
||||||
|
if (marketplace === 'WILDBERRIES' || marketplace === 'OZON') {
|
||||||
|
return (
|
||||||
<Grid item key={marketplace.name}>
|
<Grid item key={marketplace.name}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
className="marketplace-button"
|
className="marketplace-button"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: selectedMarketplace === marketplace.name ? marketplace.bgColor : '#fcfcf8',
|
backgroundColor: selectedMarketplace === marketplace ? marketplace.bgColor : '#fcfcf8',
|
||||||
color: selectedMarketplace === marketplace.name ? marketplace.textColor : '#16305e',
|
color: selectedMarketplace === marketplace ? marketplace.textColor : '#16305e',
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
padding: '1rem',
|
padding: '1rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -182,19 +196,22 @@ function HomePage() {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minWidth: '400px',
|
minWidth: '400px',
|
||||||
}}
|
}}
|
||||||
onClick={() => handleButtonClick(marketplace.name)}
|
onClick={() => handleButtonClick(marketplace)}
|
||||||
>
|
>
|
||||||
{marketplace.name === 'Wildberries' ? (
|
{marketplace === 'WILDBERRIES' ? (
|
||||||
<img src="https://png.klev.club/uploads/posts/2024-04/png-klev-club-dejs-p-wildberries-logotip-png-16.png" alt="Wildberries" style={{ width: '60px', height: '60px', marginBottom: '0.5rem' }} />
|
<img src="https://png.klev.club/uploads/posts/2024-04/png-klev-club-dejs-p-wildberries-logotip-png-16.png" alt="Wildberries" style={{ width: '60px', height: '60px', marginBottom: '0.5rem' }} />
|
||||||
) : (
|
) : (
|
||||||
<img src="https://pngimg.com/d/ozon_PNG3.png" alt="Ozon" style={{ width: '60px', height: '60px', marginBottom: '0.5rem' }} />
|
<img src="https://pngimg.com/d/ozon_PNG3.png" alt="Ozon" style={{ width: '60px', height: '60px', marginBottom: '0.5rem' }} />
|
||||||
)}
|
)}
|
||||||
<Typography variant="h5" gutterBottom style={{ color: selectedMarketplace === marketplace.name ? marketplace.textColor : '#16305e', marginBottom: '0rem' }}>
|
<Typography variant="h5" gutterBottom style={{ color: selectedMarketplace === marketplace ? marketplace.textColor : '#16305e', marginBottom: '0rem' }}>
|
||||||
{marketplace.name}
|
{marketplace}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
);
|
||||||
|
}
|
||||||
|
return null; // Для остальных названий, возвращаем null, чтобы не рендерить ничего
|
||||||
|
})}
|
||||||
</Grid>
|
</Grid>
|
||||||
<br></br>
|
<br></br>
|
||||||
{/* Комбобокс */}
|
{/* Комбобокс */}
|
||||||
@ -202,15 +219,14 @@ function HomePage() {
|
|||||||
Выберите категорию:
|
Выберите категорию:
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormControl variant="outlined" style={{ minWidth: '97%' }}>
|
<FormControl variant="outlined" style={{ minWidth: '97%' }}>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
labelId="marketplace-select-label"
|
labelId="marketplace-select-label"
|
||||||
value={selectedMarketplace}
|
value={selectedCategory}
|
||||||
onChange={handleMarketplaceChange}
|
onChange={handleCategoryChange}
|
||||||
style={{
|
style={{
|
||||||
color: '#023247',
|
color: '#023247',
|
||||||
borderColor: '#4875b2',
|
borderColor: '#4875b2',
|
||||||
borderRadius: '0.5rem'// Цвет текста выпадающего списка
|
borderRadius: '0.5rem', // Цвет текста выпадающего списка
|
||||||
}}
|
}}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
style: {
|
style: {
|
||||||
@ -226,50 +242,15 @@ function HomePage() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<MenuItem key={category.id} value={category.id}>
|
<MenuItem key={category} value={category} onChange={handleCategoryChange}>
|
||||||
{category.name}
|
{category}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<br></br>
|
<br></br>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<br></br>
|
<br></br>
|
||||||
<Typography variant="h4" gutterBottom>
|
|
||||||
Введите период для сбора данных:
|
|
||||||
</Typography>
|
|
||||||
{startDateError && (
|
|
||||||
<Alert severity="error" style={{ marginBottom: '10px' }}>
|
|
||||||
Дата начала периода не может быть после сегодняшнего дня.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{endDateError && (
|
|
||||||
<Alert severity="error" style={{ marginBottom: '10px' }}>
|
|
||||||
Дата окончания периода не может быть после сегодняшнего дня или раньше даты начала.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="date-pickers" style={{ display: 'flex', alignItems: 'center', marginTop: '0.5rem' }}>
|
|
||||||
<DatePicker
|
|
||||||
selected={startDate}
|
|
||||||
onChange={handleStartDateChange}
|
|
||||||
className="datePickerInput"
|
|
||||||
wrapperClassName="datePicker"
|
|
||||||
dateFormat="dd.MM.yyyy"
|
|
||||||
popperPlacement="bottom"
|
|
||||||
showMonthDropdown
|
|
||||||
/>
|
|
||||||
<span className="date-separator" style={{ margin: '0 0.5rem', color: '#2a8e9e', fontSize: '30px' }}> - </span>
|
|
||||||
<DatePicker
|
|
||||||
selected={endDate}
|
|
||||||
onChange={handleEndDateChange}
|
|
||||||
className="datePickerInput"
|
|
||||||
wrapperClassName="datePicker"
|
|
||||||
dateFormat="dd.MM.yyyy"
|
|
||||||
popperPlacement="bottom"
|
|
||||||
showMonthDropdown
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -280,13 +261,18 @@ function HomePage() {
|
|||||||
padding: '0.75rem 1.5rem',
|
padding: '0.75rem 1.5rem',
|
||||||
backgroundColor: '#4875b2',
|
backgroundColor: '#4875b2',
|
||||||
marginTop: '1.5rem',
|
marginTop: '1.5rem',
|
||||||
minWidth: '97%'
|
minWidth: '97%', // Можно оставить или изменить на '100%'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h5" gutterBottom style={{ color: '#fcfcfb', marginBottom: '0rem' }}>
|
<Typography variant="h5" gutterBottom style={{ color: '#fcfcfb', marginBottom: '0rem' }}>
|
||||||
Получить рекомендации
|
Получить рекомендации
|
||||||
</Typography>
|
</Typography>
|
||||||
</Button>
|
</Button>
|
||||||
|
{showResultError && (
|
||||||
|
<Alert severity="error" style={{ width: '94%', marginTop: '1rem' }}>
|
||||||
|
Выберите маркетплейс и категорию
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -306,8 +292,11 @@ function HomePage() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '97%',
|
width: '97%',
|
||||||
height: '700px',
|
height: 'auto', // Изменение на auto для высоты
|
||||||
overflow: 'hidden'
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
}}>
|
}}>
|
||||||
<img
|
<img
|
||||||
src="https://cdn.prod.website-files.com/61ebe5f773be1acd620f8208/61fb879dfccdca6a20c66d4a_e-commerce-marketplace.gif"
|
src="https://cdn.prod.website-files.com/61ebe5f773be1acd620f8208/61fb879dfccdca6a20c66d4a_e-commerce-marketplace.gif"
|
||||||
@ -315,31 +304,29 @@ function HomePage() {
|
|||||||
className="main-image"
|
className="main-image"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
maxWidth: '80%',
|
maxWidth: '66%',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
position: 'absolute',
|
|
||||||
top: '80%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -100%)'
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Typography variant="h4" style={{
|
<Typography variant="h4" style={{
|
||||||
color: '#132a52',
|
color: '#132a52',
|
||||||
position: 'absolute',
|
|
||||||
top: '60%',
|
|
||||||
width: '80%',
|
width: '80%',
|
||||||
|
textAlign: 'center',
|
||||||
}}>
|
}}>
|
||||||
Вы можете посмотреть историю изменения цены конкретного товара при помощи его URL.
|
Вы можете посмотреть историю изменения цены конкретного товара при помощи его URL.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="h4" style={{
|
<Typography variant="h4" style={{
|
||||||
color: '#132a52',
|
color: '#132a52',
|
||||||
position: 'absolute',
|
width: '80%',
|
||||||
top: '80%',
|
textAlign: 'center',
|
||||||
width: '80%'
|
marginTop: '1rem',
|
||||||
}}>
|
}}>
|
||||||
Введите ссылку на товар:
|
Введите ссылку на товар:
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Ссылка на товар"
|
label="Ссылка на товар"
|
||||||
placeholder="https://www.ozon.ru/..."
|
placeholder="https://www.ozon.ru/..."
|
||||||
@ -348,32 +335,37 @@ function HomePage() {
|
|||||||
value={productUrl}
|
value={productUrl}
|
||||||
onChange={handleProductUrlChange}
|
onChange={handleProductUrlChange}
|
||||||
style={{
|
style={{
|
||||||
marginTop: '-3.5rem',
|
marginTop: '1rem', // Теперь пространство между текстом и полем ввода
|
||||||
color: '#132a52',
|
maxWidth: '97%',
|
||||||
borderColor: '#4875b2',
|
|
||||||
borderRadius: '1rem',
|
|
||||||
maxWidth: '97%'
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button variant="contained" color="primary" onClick={handleViewPriceHistory}
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={handleViewPriceHistory}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
padding: '0.75rem 1.5rem',
|
padding: '0.75rem 1.5rem',
|
||||||
backgroundColor: '#4875b2',
|
backgroundColor: '#4875b2',
|
||||||
marginTop: '1.5rem',
|
marginTop: '1.5rem',
|
||||||
minWidth: '97%'
|
minWidth: '97%'
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Typography variant="h5" gutterBottom style={{ color: '#fcfcfb', marginBottom: '0rem' }}>
|
<Typography variant="h5" gutterBottom style={{ color: '#fcfcfb', marginBottom: '0rem' }}>
|
||||||
Посмотреть историю цены
|
Посмотреть историю цены
|
||||||
</Typography>
|
</Typography>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{showPriceHistoryError && (
|
{showPriceHistoryError && (
|
||||||
<Alert severity="error" style={{ marginTop: '1rem' }}>
|
<Alert severity="error" style={{ width: '94%', marginTop: '1rem', textAlign: 'center' }}>
|
||||||
Неверный URL товара или товар не найден.
|
Неверный URL товара или товар не найден.
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
@ -1,28 +1,49 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
import { Typography, Box, Grid, Container, Card, CardContent, CardHeader, Button } from '@mui/material';
|
import { Typography, Box, Grid, Container, Card, CardContent, CardHeader, Button, CircularProgress } from '@mui/material';
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';
|
||||||
|
|
||||||
const Result = () => {
|
const Result = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { startDate, endDate, selectedMarketplace } = location.state || {};
|
const { startDate, endDate, selectedMarketplace, selectedCategory } = location.state || {};
|
||||||
|
const [predictData, setPredictData] = useState(null);
|
||||||
const generateDates = (startDate, endDate) => {
|
const [loading, setLoading] = useState(true);
|
||||||
const dates = [];
|
const [imageUrl, setImageUrl] = useState('');
|
||||||
let currentDate = new Date(startDate);
|
useEffect(() => {
|
||||||
while (currentDate <= endDate) {
|
const fetchPredictPrice = async () => {
|
||||||
dates.push(new Date(currentDate));
|
try {
|
||||||
currentDate.setDate(currentDate.getDate() + 1);
|
const response = await fetch('http://localhost:5000/api/predict_price');
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
setPredictData(data);
|
||||||
|
} else {
|
||||||
|
console.error("Ошибка запроса к API");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка при получении данных:", error);
|
||||||
}
|
}
|
||||||
return dates;
|
|
||||||
};
|
};
|
||||||
|
const fetchChart = async () => {
|
||||||
const data = generateDates(startDate, endDate).map((date, index) => ({
|
try {
|
||||||
date: date,
|
const response = await fetch('http://localhost:5000/api/plot');
|
||||||
price: Math.floor(Math.random() * 200) + 50
|
if (!response.ok) {
|
||||||
}));
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
const blob = await response.blob();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
setImageUrl(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при загрузке графика:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchChart();
|
||||||
|
fetchPredictPrice();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
@ -72,7 +93,7 @@ const Result = () => {
|
|||||||
Выбранный маркетплейс: {selectedMarketplace}
|
Выбранный маркетплейс: {selectedMarketplace}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" gutterBottom style={{ color: '#023247' }}>
|
<Typography variant="body1" gutterBottom style={{ color: '#023247' }}>
|
||||||
Период: {startDate?.toLocaleDateString()} по {endDate?.toLocaleDateString()}
|
Выбранная категория: {selectedCategory}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -82,19 +103,11 @@ const Result = () => {
|
|||||||
<Card style={{ backgroundColor: '#fcfcf8', borderRadius: '1rem' }}>
|
<Card style={{ backgroundColor: '#fcfcf8', borderRadius: '1rem' }}>
|
||||||
<CardHeader title="Анализ" style={{ color: '#023247' }} />
|
<CardHeader title="Анализ" style={{ color: '#023247' }} />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<LineChart width={600} height={300} data={data}>
|
{loading ? (
|
||||||
<XAxis
|
<CircularProgress />
|
||||||
dataKey="date"
|
) : (
|
||||||
tickFormatter={(unixTime) => new Date(unixTime).toLocaleDateString()}
|
<img src={imageUrl} alt="График предсказанных и фактических цен" style={{ width: '100%', borderRadius: '1rem' }} />
|
||||||
tickMargin={10}
|
)}
|
||||||
stroke="#023247"
|
|
||||||
/>
|
|
||||||
<YAxis stroke="#023247" />
|
|
||||||
<CartesianGrid stroke="#f5f5f5" />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Line type="monotone" dataKey="price" stroke="#2a8e9e" activeDot={{ r: 8 }} />
|
|
||||||
</LineChart>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -102,10 +115,13 @@ const Result = () => {
|
|||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Card style={{ backgroundColor: '#fcfcf8', borderRadius: '1rem' }}>
|
<Card style={{ backgroundColor: '#fcfcf8', borderRadius: '1rem' }}>
|
||||||
<CardHeader title="Рекомендации" style={{ color: '#023247' }} />
|
<CardHeader title="Рекомендации" style={{ color: '#023247' }} />
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="body1" gutterBottom style={{ color: '#023247' }}>
|
<Typography variant="body1" gutterBottom style={{ color: '#023247' }}>
|
||||||
Здесь будут отображаться рекомендации, основанные на анализе данных.
|
{predictData ? (
|
||||||
|
`Продукт лучше покупать в ${predictData.min_price_day.date}. Предсказанная цена: ${predictData.min_price_day.price} руб.`
|
||||||
|
) : (
|
||||||
|
"Загрузка данных..."
|
||||||
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -31,16 +31,24 @@ const ViewProduct = () => {
|
|||||||
|
|
||||||
const sign = zoneOffset >= 0 ? '+' : '-';
|
const sign = zoneOffset >= 0 ? '+' : '-';
|
||||||
|
|
||||||
|
// Исправлено: использование шаблонных строк для форматирования часового пояса
|
||||||
const formattedZoneOffset = `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
const formattedZoneOffset = `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
const { productUrl, from, to } = location.state || {};
|
// Определение 'to' как текущая дата
|
||||||
|
const to = date.toISOString().split('T')[0]; // Текущая дата в формате YYYY-MM-DD
|
||||||
|
const from = new Date(date);
|
||||||
|
from.setDate(date.getDate() - 30); // Дата минус 30 дней
|
||||||
|
const formattedFrom = from.toISOString().split('T')[0]; // Дата в формате YYYY-MM-DD
|
||||||
|
|
||||||
|
// Извлечение productUrl из состояния location
|
||||||
|
const { productUrl } = location.state || {};
|
||||||
const [productData, setProductData] = useState(null);
|
const [productData, setProductData] = useState(null);
|
||||||
const [chartData, setChartData] = useState([]);
|
const [chartData, setChartData] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchProductData = async () => {
|
const fetchProductData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/products/info?productUrl=${productUrl}`);
|
const response = await fetch(`https://mgpj3mxm-8080.euw.devtunnels.ms/api/v1/products/info?productUrl=${productUrl}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setProductData(data);
|
setProductData(data);
|
||||||
@ -54,14 +62,18 @@ const ViewProduct = () => {
|
|||||||
|
|
||||||
const fetchPriceHistory = async () => {
|
const fetchPriceHistory = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/products/price-history?productUrl=${productUrl}&from=${from}&to=${to}&zoneOffset=${formattedZoneOffset}`);
|
const response = await fetch(`https://mgpj3mxm-8080.euw.devtunnels.ms/api/v1/products/price-history?productUrl=${productUrl}&from=${formattedFrom}&to=${to}&zoneOffset=${formattedZoneOffset}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const priceHistoryData = await response.json();
|
const priceHistoryData = await response.json();
|
||||||
|
// Преобразование данных в нужный формат
|
||||||
const priceHistory = Object.entries(priceHistoryData.priceHistory).map(([date, price]) => ({
|
const priceHistory = Object.entries(priceHistoryData.priceHistory).map(([date, price]) => ({
|
||||||
date: new Date(date),
|
date: new Date(date), // Преобразование строки даты в объект Date
|
||||||
price: price,
|
price: price,
|
||||||
}));
|
}));
|
||||||
setChartData(priceHistory);
|
|
||||||
|
// Сортировка по дате
|
||||||
|
const sortedPriceHistory = priceHistory.sort((a, b) => a.date - b.date);
|
||||||
|
setChartData(sortedPriceHistory);
|
||||||
} else {
|
} else {
|
||||||
console.error("Ошибка запроса к API");
|
console.error("Ошибка запроса к API");
|
||||||
}
|
}
|
||||||
@ -70,11 +82,10 @@ const ViewProduct = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (productUrl) {
|
|
||||||
fetchProductData();
|
fetchProductData();
|
||||||
fetchPriceHistory();
|
fetchPriceHistory();
|
||||||
}
|
}, [productUrl, formattedFrom, to, formattedZoneOffset]);
|
||||||
}, [productUrl, from, to, formattedZoneOffset]);
|
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
@ -127,13 +138,10 @@ const ViewProduct = () => {
|
|||||||
{productData.marketplaceName}
|
{productData.marketplaceName}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" gutterBottom style={{ color: theme.palette.secondary.main }}>
|
<Typography variant="body1" gutterBottom style={{ color: theme.palette.secondary.main }}>
|
||||||
<a href={productData.link} target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.primary.main, textDecoration: 'underline' }}>
|
<a href={productUrl} target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.primary.main, textDecoration: 'underline' }}>
|
||||||
Ссылка на товар
|
Перейти на страницу товара
|
||||||
</a>
|
</a>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" gutterBottom style={{ color: theme.palette.secondary.main, fontWeight: 'bold' }}>
|
|
||||||
{productData.brand}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1" gutterBottom style={{ color: theme.palette.secondary.main, fontWeight: 'bold' }}>
|
<Typography variant="body1" gutterBottom style={{ color: theme.palette.secondary.main, fontWeight: 'bold' }}>
|
||||||
{productData.productName}
|
{productData.productName}
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -165,6 +173,7 @@ const ViewProduct = () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
Loading…
Reference in New Issue
Block a user