Add new frontend - fix 9
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
ArrowPathIcon,
|
||||
ScaleIcon, // For accuracy
|
||||
CalendarDaysIcon, // For last training date
|
||||
CurrencyEuroIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import { useTrainingProgress } from '../../api/hooks/useTrainingProgress'; // Path corrected
|
||||
@@ -17,14 +18,17 @@ import { ForecastChart } from '../../components/charts/ForecastChart';
|
||||
import { SalesUploader } from '../../components/data/SalesUploader';
|
||||
import { NotificationToast } from '../../components/common/NotificationToast';
|
||||
import { ErrorBoundary } from '../../components/common/ErrorBoundary';
|
||||
import { defaultProducts } from '../../components/common/ProductSelector';
|
||||
import {
|
||||
dataApi,
|
||||
forecastingApi,
|
||||
ApiResponse,
|
||||
ForecastRecord,
|
||||
SalesRecord,
|
||||
TrainingRequest,
|
||||
} from '../../api/services/api'; // Consolidated API services and types
|
||||
TrainingJobProgress
|
||||
} from '@/api/services';
|
||||
|
||||
|
||||
import api from '@/api/services';
|
||||
|
||||
|
||||
// Dashboard specific types
|
||||
interface DashboardStats {
|
||||
@@ -141,7 +145,7 @@ const DashboardPage: React.FC = () => {
|
||||
setLoadingData(true);
|
||||
try {
|
||||
// Fetch Dashboard Stats
|
||||
const statsResponse: ApiResponse<DashboardStats> = await dataApi.getDashboardStats();
|
||||
const statsResponse: ApiResponse<DashboardStats> = await api.data.dataApi.getDashboardStats();
|
||||
if (statsResponse.data) {
|
||||
setStats(statsResponse.data);
|
||||
} else if (statsResponse.message) {
|
||||
@@ -149,7 +153,7 @@ const DashboardPage: React.FC = () => {
|
||||
}
|
||||
|
||||
// Fetch initial forecasts (e.g., for a default product or the first available product)
|
||||
const forecastResponse: ApiResponse<ForecastRecord[]> = await forecastingApi.getForecast({
|
||||
const forecastResponse: ApiResponse<ForecastRecord[]> = await api.forecasting.getForecast({
|
||||
forecast_days: 7, // Example: 7 days forecast
|
||||
product_name: user?.tenant_id ? 'pan' : undefined, // Default to 'pan' or first product
|
||||
});
|
||||
@@ -177,7 +181,7 @@ const DashboardPage: React.FC = () => {
|
||||
const handleSalesUpload = async (file: File) => {
|
||||
try {
|
||||
addNotification('info', 'Subiendo archivo', 'Cargando historial de ventas...');
|
||||
const response = await dataApi.uploadSalesHistory(file);
|
||||
const response = await api.data.dataApi.uploadSalesHistory(file);
|
||||
addNotification('success', 'Subida Completa', 'Historial de ventas cargado exitosamente.');
|
||||
|
||||
// After upload, trigger a new training (assuming this is the flow)
|
||||
@@ -186,9 +190,9 @@ const DashboardPage: React.FC = () => {
|
||||
// You might want to specify products if the uploader supports it,
|
||||
// or let the backend determine based on the uploaded data.
|
||||
};
|
||||
const trainingTask: TrainingTask = await trainingApi.startTraining(trainingRequest);
|
||||
setActiveJobId(trainingTask.job_id);
|
||||
addNotification('info', 'Entrenamiento iniciado', `Un nuevo entrenamiento ha comenzado (ID: ${trainingTask.job_id}).`);
|
||||
const trainingTask: TrainingJobProgress = await api.training.trainingApi.startTraining(trainingRequest);
|
||||
setActiveJobId(trainingTask.id);
|
||||
addNotification('info', 'Entrenamiento iniciado', `Un nuevo entrenamiento ha comenzado (ID: ${trainingTask.id}).`);
|
||||
// No need to fetch dashboard data here, as useEffect for isTrainingComplete will handle it
|
||||
} catch (error: any) {
|
||||
console.error('Error uploading sales or starting training:', error);
|
||||
@@ -199,7 +203,7 @@ const DashboardPage: React.FC = () => {
|
||||
const handleForecastProductChange = async (productName: string) => {
|
||||
setLoadingData(true);
|
||||
try {
|
||||
const forecastResponse: ApiResponse<ForecastRecord[]> = await forecastingApi.getForecast({
|
||||
const forecastResponse: ApiResponse<ForecastRecord[]> = await api.forecasting.forecastingApi.getForecast({
|
||||
forecast_days: 7,
|
||||
product_name: productName,
|
||||
});
|
||||
@@ -280,7 +284,7 @@ const DashboardPage: React.FC = () => {
|
||||
<StatsCard
|
||||
title="Ingresos Totales"
|
||||
value={stats?.totalRevenue}
|
||||
icon={CurrencyEuroIcon} {/* Assuming CurrencyEuroIcon from heroicons */}
|
||||
icon={CurrencyEuroIcon}
|
||||
format="currency"
|
||||
loading={loadingData}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// frontend/src/pages/onboarding.tsx - ORIGINAL DESIGN WITH AUTH FIXES ONLY
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Head from 'next/head';
|
||||
import {
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
import { SalesUploader } from '../components/data/SalesUploader';
|
||||
import { TrainingProgressCard } from '../components/training/TrainingProgressCard';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { RegisterData } from '../api/services/authService';
|
||||
import { dataApi, TrainingRequest, TrainingTask } from '../api/services/api';
|
||||
import { NotificationToast } from '../components/common/NotificationToast';
|
||||
import { Product, defaultProducts } from '../components/common/ProductSelector';
|
||||
@@ -76,6 +75,76 @@ const OnboardingPage: React.FC = () => {
|
||||
|
||||
const [errors, setErrors] = useState<Partial<OnboardingFormData>>({});
|
||||
|
||||
const addressInputRef = useRef<HTMLInputElement>(null); // Ref for the address input
|
||||
let autocompleteTimeout: NodeJS.Timeout | null = null; // For debouncing API calls
|
||||
|
||||
const handleAddressInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const query = e.target.value;
|
||||
setFormData(prevData => ({ ...prevData, address: query })); // Update address immediately
|
||||
|
||||
if (autocompleteTimeout) {
|
||||
clearTimeout(autocompleteTimeout);
|
||||
}
|
||||
|
||||
if (query.length < 3) { // Only search if at least 3 characters are typed
|
||||
return;
|
||||
}
|
||||
|
||||
autocompleteTimeout = setTimeout(async () => {
|
||||
try {
|
||||
// Construct the Nominatim API URL
|
||||
// Make sure NOMINATIM_PORT matches your .env file, default is 8080
|
||||
const gatewayNominatimApiUrl = `/api/v1/nominatim/search`; // Relative path if frontend serves from gateway's domain/port
|
||||
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
format: 'json',
|
||||
addressdetails: '1', // Request detailed address components
|
||||
limit: '5', // Number of results to return
|
||||
'accept-language': 'es', // Request results in Spanish
|
||||
countrycodes: 'es' // Restrict search to Spain
|
||||
});
|
||||
|
||||
const response = await fetch(`${gatewayNominatimApiUrl}?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Process Nominatim results and update form data
|
||||
if (data && data.length > 0) {
|
||||
// Take the first result or let the user choose from suggestions if you implement a dropdown
|
||||
const place = data[0]; // For simplicity, take the first result
|
||||
|
||||
let address = '';
|
||||
let city = '';
|
||||
let postal_code = '';
|
||||
|
||||
// Nominatim's 'address' object contains components
|
||||
if (place.address) {
|
||||
const addr = place.address;
|
||||
|
||||
// Reconstruct the address in a common format
|
||||
const street = addr.road || '';
|
||||
const houseNumber = addr.house_number || '';
|
||||
address = `${street} ${houseNumber}`.trim();
|
||||
|
||||
city = addr.city || addr.town || addr.village || '';
|
||||
postal_code = addr.postcode || '';
|
||||
}
|
||||
|
||||
setFormData(prevData => ({
|
||||
...prevData,
|
||||
address: address || query, // Use parsed address or fall back to user input
|
||||
city: city || prevData.city,
|
||||
postal_code: postal_code || prevData.postal_code,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching Nominatim suggestions:', error);
|
||||
// Optionally show an error notification
|
||||
// showNotification('error', 'Error de Autocompletado', 'No se pudieron cargar las sugerencias de dirección.');
|
||||
}
|
||||
}, 500); // Debounce time: 500ms
|
||||
}, []); // Re-create if dependencies change, none for now
|
||||
|
||||
useEffect(() => {
|
||||
// If user is already authenticated and on onboarding, redirect to dashboard
|
||||
if (user && currentStep === 1) {
|
||||
@@ -106,7 +175,7 @@ const OnboardingPage: React.FC = () => {
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const registerData: RegisterData = {
|
||||
const registerData: dataApi.auth.RegisterData = {
|
||||
full_name: formData.full_name,
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
@@ -287,9 +356,11 @@ const OnboardingPage: React.FC = () => {
|
||||
<input
|
||||
type="text"
|
||||
id="address"
|
||||
ref={addressInputRef}
|
||||
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:ring-pania-blue focus:border-pania-blue sm:text-sm"
|
||||
value={formData.address}
|
||||
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
||||
// Use the new handler for changes to trigger autocomplete
|
||||
onChange={handleAddressInputChange}
|
||||
required
|
||||
/>
|
||||
{errors.address && <p className="mt-1 text-sm text-red-600">{errors.address}</p>}
|
||||
|
||||
Reference in New Issue
Block a user