diff --git a/analysis/app.py b/analysis/app.py index 3299759..55afec2 100644 --- a/analysis/app.py +++ b/analysis/app.py @@ -8,12 +8,13 @@ import io import joblib from flask import Flask, request, jsonify, Blueprint, send_file from flasgger import Swagger +from flask_cors import CORS app = Flask(__name__) api = Blueprint('api', __name__) Swagger(app) - +CORS(app) # Загружаем модель и scaler model = load_model("my_model_1H.keras") scaler = MinMaxScaler(feature_range=(0, 1)) diff --git a/my-app/src/App.js b/my-app/src/App.js index c92ca8b..1d1e2d1 100644 --- a/my-app/src/App.js +++ b/my-app/src/App.js @@ -16,11 +16,13 @@ function HomePage() { const [marketplaces, setMarketplaces] = useState([]); const [productUrl, setProductUrl] = useState(''); const [showPriceHistoryError, setShowPriceHistoryError] = useState(false); + const [showResultError, setShowResultError] = useState(false); + const [selectedCategory, setSelectedCategory] = useState(''); useEffect(() => { const fetchMarketplaces = async () => { 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); } catch (error) { console.error('Error fetching marketplaces:', error); @@ -34,7 +36,8 @@ function HomePage() { const fetchCategories = async () => { try { 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); } } catch (error) { @@ -46,10 +49,17 @@ function HomePage() { }, [selectedMarketplace]); const handleSubmit = () => { - console.log('Отправлено:', startDate, endDate, selectedMarketplace); - navigate('/result', { - state: { startDate, endDate, selectedMarketplace } - }); + if (selectedCategory !== "" || selectedMarketplace !== "") { + navigate('/result', { + state: { startDate, endDate, selectedMarketplace, selectedCategory } + }); + } else { + setShowResultError(true); + } + }; + + const handleCategoryChange = (event) => { + setSelectedCategory(event.target.value); }; const handleProductUrlChange = (event) => { @@ -64,7 +74,8 @@ function HomePage() { // Проверка существования товара по ссылке 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) { // Товар найден navigate('/viewProduct', { state: { productUrl } }); @@ -83,6 +94,7 @@ function HomePage() { setSelectedMarketplace(''); } else { setSelectedMarketplace(marketplaceName); + setSelectedCategory(''); } }; @@ -146,7 +158,7 @@ function HomePage() { height: '90%', display: 'flex', flexDirection: 'column', - justifyContent: 'center', + justifyContent: 'center' }} > @@ -165,36 +177,41 @@ function HomePage() { Выберите маркетплейсы, которые вас интересуют: - {marketplaces.map((marketplace) => ( - - - - ))} + {marketplaces.map((marketplace) => { + if (marketplace === 'WILDBERRIES' || marketplace === 'OZON') { + return ( + + + + ); + } + return null; // Для остальных названий, возвращаем null, чтобы не рендерить ничего + })}

{/* Комбобокс */} @@ -202,15 +219,14 @@ function HomePage() { Выберите категорию: -



- - Введите период для сбора данных: - - {startDateError && ( - - Дата начала периода не может быть после сегодняшнего дня. - - )} - {endDateError && ( - - Дата окончания периода не может быть после сегодняшнего дня или раньше даты начала. - - )} - -
- - - - -
+ {showResultError && ( + + Выберите маркетплейс и категорию + + )} @@ -306,8 +292,11 @@ function HomePage() {
- - Вы можете посмотреть историю изменения цены конкретного товара при помощи его URL. - - - Введите ссылку на товар: -
+ + + Вы можете посмотреть историю изменения цены конкретного товара при помощи его URL. + + + + Введите ссылку на товар: + + - + {showPriceHistoryError && ( - + Неверный URL товара или товар не найден. )} + + diff --git a/my-app/src/Result.js b/my-app/src/Result.js index 9fa4ca1..27af097 100644 --- a/my-app/src/Result.js +++ b/my-app/src/Result.js @@ -1,28 +1,49 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; 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'; const Result = () => { const location = useLocation(); const navigate = useNavigate(); - const { startDate, endDate, selectedMarketplace } = location.state || {}; - - const generateDates = (startDate, endDate) => { - const dates = []; - let currentDate = new Date(startDate); - while (currentDate <= endDate) { - dates.push(new Date(currentDate)); - currentDate.setDate(currentDate.getDate() + 1); - } - return dates; - }; - - const data = generateDates(startDate, endDate).map((date, index) => ({ - date: date, - price: Math.floor(Math.random() * 200) + 50 - })); + const { startDate, endDate, selectedMarketplace, selectedCategory } = location.state || {}; + const [predictData, setPredictData] = useState(null); + const [loading, setLoading] = useState(true); + const [imageUrl, setImageUrl] = useState(''); + useEffect(() => { + const fetchPredictPrice = async () => { + try { + 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); + } + }; + const fetchChart = async () => { + try { + const response = await fetch('http://localhost:5000/api/plot'); + 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({ palette: { @@ -72,8 +93,8 @@ const Result = () => { Выбранный маркетплейс: {selectedMarketplace} - Период: {startDate?.toLocaleDateString()} по {endDate?.toLocaleDateString()} - + Выбранная категория: {selectedCategory} + @@ -81,31 +102,26 @@ const Result = () => { - - - new Date(unixTime).toLocaleDateString()} - tickMargin={10} - stroke="#023247" - /> - - - - - - - + + {loading ? ( + + ) : ( + График предсказанных и фактических цен + )} + - - Здесь будут отображаться рекомендации, основанные на анализе данных. + {predictData ? ( + `Продукт лучше покупать в ${predictData.min_price_day.date}. Предсказанная цена: ${predictData.min_price_day.price} руб.` + ) : ( + "Загрузка данных..." + )} diff --git a/my-app/src/ViewProduct.js b/my-app/src/ViewProduct.js index 1c4360c..12333d0 100644 --- a/my-app/src/ViewProduct.js +++ b/my-app/src/ViewProduct.js @@ -31,16 +31,24 @@ const ViewProduct = () => { const sign = zoneOffset >= 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 [chartData, setChartData] = useState([]); useEffect(() => { const fetchProductData = async () => { 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) { const data = await response.json(); setProductData(data); @@ -54,14 +62,18 @@ const ViewProduct = () => { const fetchPriceHistory = async () => { 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) { const priceHistoryData = await response.json(); + // Преобразование данных в нужный формат const priceHistory = Object.entries(priceHistoryData.priceHistory).map(([date, price]) => ({ - date: new Date(date), + date: new Date(date), // Преобразование строки даты в объект Date price: price, })); - setChartData(priceHistory); + + // Сортировка по дате + const sortedPriceHistory = priceHistory.sort((a, b) => a.date - b.date); + setChartData(sortedPriceHistory); } else { console.error("Ошибка запроса к API"); } @@ -70,11 +82,10 @@ const ViewProduct = () => { } }; - if (productUrl) { - fetchProductData(); - fetchPriceHistory(); - } - }, [productUrl, from, to, formattedZoneOffset]); + fetchProductData(); + fetchPriceHistory(); + }, [productUrl, formattedFrom, to, formattedZoneOffset]); + const theme = createTheme({ palette: { @@ -127,13 +138,10 @@ const ViewProduct = () => { {productData.marketplaceName} - - Ссылка на товар + + Перейти на страницу товара - - {productData.brand} - {productData.productName} @@ -165,6 +173,7 @@ const ViewProduct = () => { + )}