// frontend/src/api/services/data.service.ts /** * Data Management Service * Handles sales data operations */ import { apiClient } from '../client'; import { RequestTimeouts } from '../client/config'; import type { SalesData, SalesValidationResult, SalesDataQuery, SalesDataImport, SalesImportResult, DashboardStats, PaginatedResponse, ActivityItem, } from '../types'; export class DataService { /** * Upload Sales History File */ async uploadSalesHistory( tenantId: string, file: File, additionalData?: Record ): Promise { // Determine file format const fileName = file.name.toLowerCase(); let fileFormat: string; if (fileName.endsWith('.csv')) { fileFormat = 'csv'; } else if (fileName.endsWith('.json')) { fileFormat = 'json'; } else if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) { fileFormat = 'excel'; } else { fileFormat = 'csv'; // Default fallback } const uploadData = { file_format: fileFormat, ...additionalData, }; return apiClient.upload( `/tenants/${tenantId}/sales/import`, file, uploadData, { timeout: RequestTimeouts.LONG, } ); } /** * Validate Sales Data */ async validateSalesData( tenantId: string, file: File ): Promise { const fileName = file.name.toLowerCase(); let fileFormat: string; if (fileName.endsWith('.csv')) { fileFormat = 'csv'; } else if (fileName.endsWith('.json')) { fileFormat = 'json'; } else if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) { fileFormat = 'excel'; } else { fileFormat = 'csv'; } return apiClient.upload( `/tenants/${tenantId}/sales/import/validate`, file, { file_format: fileFormat, validate_only: true, source: 'onboarding_upload', }, { timeout: RequestTimeouts.MEDIUM, } ); } /** * Get Sales Data */ async getSalesData( tenantId: string, query?: SalesDataQuery ): Promise> { return apiClient.get(`/tenants/${tenantId}/sales`, { params: query }); } /** * Get Single Sales Record */ async getSalesRecord(tenantId: string, recordId: string): Promise { return apiClient.get(`/tenants/${tenantId}/sales/${recordId}`); } /** * Update Sales Record */ async updateSalesRecord( tenantId: string, recordId: string, data: Partial ): Promise { return apiClient.put(`/tenants/${tenantId}/sales/${recordId}`, data); } /** * Delete Sales Record */ async deleteSalesRecord(tenantId: string, recordId: string): Promise<{ message: string }> { return apiClient.delete(`/tenants/${tenantId}/sales/${recordId}`); } /** * Get Dashboard Statistics */ async getDashboardStats(tenantId: string): Promise { return apiClient.get(`/tenants/${tenantId}/sales/stats`); } /** * Get Analytics Data */ async getAnalytics( tenantId: string, params?: { start_date?: string; end_date?: string; product_names?: string[]; metrics?: string[]; } ): Promise { return apiClient.get(`/tenants/${tenantId}/analytics`, { params }); } /** * Export Sales Data */ async exportSalesData( tenantId: string, format: 'csv' | 'excel' | 'json', query?: SalesDataQuery ): Promise { const response = await apiClient.request(`/tenants/${tenantId}/sales/export`, { method: 'GET', params: { ...query, format }, headers: { 'Accept': format === 'csv' ? 'text/csv' : format === 'excel' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'application/json', }, }); return new Blob([response], { type: format === 'csv' ? 'text/csv' : format === 'excel' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'application/json', }); } /** * Get Recent Activity */ async getRecentActivity(tenantId: string, limit?: number): Promise { return apiClient.get(`/tenants/${tenantId}/activity`, { params: { limit }, }); } /** * Get Products List from Sales Data * This should be added to the DataService class */ async getProductsList(tenantId: string): Promise { try { const response = await apiClient.get(`/tenants/${tenantId}/sales/products`); console.log('🔍 Products API Response Analysis:'); console.log('- Type:', typeof response); console.log('- Is Array:', Array.isArray(response)); console.log('- Keys:', Object.keys(response || {})); console.log('- Response:', response); let productsArray: any[] = []; // ✅ FIX: Handle different response formats if (Array.isArray(response)) { // Standard array response productsArray = response; console.log('✅ Response is already an array'); } else if (response && typeof response === 'object') { // Object with numeric keys - convert to array const keys = Object.keys(response); if (keys.length > 0 && keys.every(key => !isNaN(Number(key)))) { // Object has numeric keys like {0: {...}, 1: {...}} productsArray = Object.values(response); console.log('✅ Converted object with numeric keys to array'); } else { console.warn('⚠️ Response is object but not with numeric keys:', response); return []; } } else { console.warn('⚠️ Response is not array or object:', response); return []; } console.log('📦 Products array:', productsArray); // Extract product names from the array const productNames = productsArray .map((product: any) => { if (typeof product === 'string') { return product; } if (product && typeof product === 'object') { return product.product_name || product.name || product.productName || null; } return null; }) .filter(Boolean) // Remove null/undefined values .filter((name: string) => name.trim().length > 0); // Remove empty strings console.log('📋 Extracted product names:', productNames); if (productNames.length === 0) { console.warn('⚠️ No valid product names extracted from response'); } return productNames; } catch (error) { console.error('❌ Failed to fetch products list:', error); // Return fallback products for Madrid bakery return [ 'Croissants', 'Pan de molde', 'Baguettes', 'Café', 'Napolitanas', 'Pan integral', 'Magdalenas', 'Churros' ]; } } /** * Get Current Weather Data * This should be added to the DataService class */ async getCurrentWeather( tenantId: string, lat: number, lon: number ): Promise<{ temperature: number; description: string; precipitation: number; humidity?: number; wind_speed?: number; }> { try { // ✅ FIX 1: Correct endpoint path with tenant ID const endpoint = `/tenants/${tenantId}/weather/current`; // ✅ FIX 2: Correct parameter names (latitude/longitude, not lat/lon) const response = await apiClient.get(endpoint, { params: { latitude: lat, // Backend expects 'latitude' longitude: lon // Backend expects 'longitude' } }); // ✅ FIX 3: Handle the actual backend response structure // Backend returns WeatherDataResponse: // { // "date": "2025-08-04T12:00:00Z", // "temperature": 25.5, // "precipitation": 0.0, // "humidity": 65.0, // "wind_speed": 10.2, // "pressure": 1013.2, // "description": "Partly cloudy", // "source": "aemet" // } console.log('Weather API response:', response); // Map backend response to expected frontend format return { temperature: response.temperature || 18, description: response.description || 'Parcialmente nublado', precipitation: response.precipitation || 0, humidity: response.humidity || 65, wind_speed: response.wind_speed || 10 }; } catch (error) { console.error('Failed to fetch weather from backend:', error); // Fallback weather for Madrid return { temperature: 18, description: 'Parcialmente nublado', precipitation: 0, humidity: 65, wind_speed: 10 }; } } /** * Get Weather Forecast * This should be added to the DataService class */ async getWeatherForecast( lat: number, lon: number, days: number = 7 ): Promise { return apiClient.get(`/data/weather/forecast`, { params: { lat, lon, days } }); } /** * Get Sales Summary by Period * This should be added to the DataService class */ async getSalesSummary( tenantId: string, period: 'daily' | 'weekly' | 'monthly' = 'daily' ): Promise { return apiClient.get(`/tenants/${tenantId}/sales/summary`, { params: { period } }); } /** * Get Sales Analytics * This should be added to the DataService class */ async getSalesAnalytics( tenantId: string, startDate?: string, endDate?: string ): Promise<{ total_revenue: number; waste_reduction_percentage?: number; forecast_accuracy?: number; stockout_events?: number; }> { return apiClient.get(`/tenants/${tenantId}/sales/analytics`, { params: { start_date: startDate, end_date: endDate } }); } } export const dataService = new DataService();